Example #1
0
def update_message_backend(request, user_profile,
                           message_id=REQ(converter=to_non_negative_int),
                           subject=REQ(default=None),
                           propagate_mode=REQ(default="change_one"),
                           content=REQ(default=None)):
    if subject is None and content is None:
        return json_error(_("Nothing to change"))
    do_update_message(user_profile, message_id, subject, propagate_mode, content)
    return json_success()
Example #2
0
def update_message_backend(request, user_profile,
                           message_id=REQ(converter=to_non_negative_int),
                           subject=REQ(default=None),
                           propagate_mode=REQ(default="change_one"),
                           content=REQ(default=None)):
    if subject is None and content is None:
        return json_error("Nothing to change")
    do_update_message(user_profile, message_id, subject, propagate_mode, content)
    return json_success()
Example #3
0
def update_message_backend(request, user_profile,
                           message_id=REQ(converter=to_non_negative_int),
                           subject=REQ(default=None),
                           propagate_mode=REQ(default="change_one"),
                           content=REQ(default=None)):
    # type: (HttpRequest, UserProfile, int, Optional[text_type], Optional[str], Optional[text_type]) -> HttpResponse
    if subject is None and content is None:
        return json_error(_("Nothing to change"))
    do_update_message(user_profile, message_id, subject, propagate_mode, content)
    return json_success()
Example #4
0
def update_message_backend(request, user_profile,
                           message_id=REQ(converter=to_non_negative_int),
                           subject=REQ(default=None),
                           propagate_mode=REQ(default="change_one"),
                           content=REQ(default=None)):
    # type: (HttpRequest, UserProfile, int, Optional[text_type], Optional[str], Optional[text_type]) -> HttpResponse
    if subject is None and content is None:
        return json_error(_("Nothing to change"))
    do_update_message(user_profile, message_id, subject, propagate_mode, content)
    return json_success()
Example #5
0
def update_message_backend(request, user_profile,
                           message_id=REQ(converter=to_non_negative_int),
                           subject=REQ(default=None),
                           propagate_mode=REQ(default="change_one"),
                           content=REQ(default=None)):
    # type: (HttpRequest, UserProfile, int, Optional[text_type], Optional[str], Optional[text_type]) -> HttpResponse
    if not user_profile.realm.allow_message_editing:
        return json_error(_("Your organization has turned off message editing."))

    try:
        message = Message.objects.select_related().get(id=message_id)
    except Message.DoesNotExist:
        raise JsonableError(_("Unknown message id"))

    # You only have permission to edit a message if:
    # 1. You sent it, OR:
    # 2. This is a topic-only edit for a (no topic) message, OR:
    # 3. This is a topic-only edit and you are an admin.
    if message.sender == user_profile:
        pass
    elif (content is None) and ((message.topic_name() == "(no topic)") or
                                user_profile.is_realm_admin):
        pass
    else:
        raise JsonableError(_("You don't have permission to edit this message"))

    # If there is a change to the content, check that it hasn't been too long
    # Allow an extra 20 seconds since we potentially allow editing 15 seconds
    # past the limit, and in case there are network issues, etc. The 15 comes
    # from (min_seconds_to_edit + seconds_left_buffer) in message_edit.js; if
    # you change this value also change those two parameters in message_edit.js.
    edit_limit_buffer = 20
    if content is not None and user_profile.realm.message_content_edit_limit_seconds > 0:
        deadline_seconds = user_profile.realm.message_content_edit_limit_seconds + edit_limit_buffer
        if (now() - message.pub_date) > datetime.timedelta(seconds=deadline_seconds):
            raise JsonableError(_("The time limit for editing this message has past"))

    if subject is None and content is None:
        return json_error(_("Nothing to change"))
    if subject is not None:
        subject = subject.strip()
        if subject == "":
            raise JsonableError(_("Topic can't be empty"))
    rendered_content = None
    if content is not None:
        content = content.strip()
        if content == "":
            raise JsonableError(_("Content can't be empty"))
        content = truncate_body(content)
        rendered_content = message.render_markdown(content)
        if not rendered_content:
            raise JsonableError(_("We were unable to render your updated message"))

    do_update_message(user_profile, message, subject, propagate_mode, content, rendered_content)
    return json_success()
Example #6
0
def update_message_backend(request, user_profile,
                           message_id=REQ(converter=to_non_negative_int),
                           subject=REQ(default=None),
                           propagate_mode=REQ(default="change_one"),
                           content=REQ(default=None)):
    # type: (HttpRequest, UserProfile, int, Optional[text_type], Optional[str], Optional[text_type]) -> HttpResponse
    if not user_profile.realm.allow_message_editing:
        return json_error(_("Your organization has turned off message editing."))

    try:
        message = Message.objects.select_related().get(id=message_id)
    except Message.DoesNotExist:
        raise JsonableError(_("Unknown message id"))

    # You only have permission to edit a message if:
    # 1. You sent it, OR:
    # 2. This is a topic-only edit for a (no topic) message, OR:
    # 3. This is a topic-only edit and you are an admin.
    if message.sender == user_profile:
        pass
    elif (content is None) and ((message.topic_name() == "(no topic)") or
                                user_profile.is_realm_admin):
        pass
    else:
        raise JsonableError(_("You don't have permission to edit this message"))

    # If there is a change to the content, check that it hasn't been too long
    # Allow an extra 20 seconds since we potentially allow editing 15 seconds
    # past the limit, and in case there are network issues, etc. The 15 comes
    # from (min_seconds_to_edit + seconds_left_buffer) in message_edit.js; if
    # you change this value also change those two parameters in message_edit.js.
    edit_limit_buffer = 20
    if content is not None and user_profile.realm.message_content_edit_limit_seconds > 0:
        deadline_seconds = user_profile.realm.message_content_edit_limit_seconds + edit_limit_buffer
        if (now() - message.pub_date) > datetime.timedelta(seconds=deadline_seconds):
            raise JsonableError(_("The time limit for editing this message has past"))

    if subject is None and content is None:
        return json_error(_("Nothing to change"))
    if subject is not None:
        subject = subject.strip()
        if subject == "":
            raise JsonableError(_("Topic can't be empty"))
    rendered_content = None
    if content is not None:
        content = content.strip()
        if content == "":
            raise JsonableError(_("Content can't be empty"))
        content = truncate_body(content)
        rendered_content = message.render_markdown(content)
        if not rendered_content:
            raise JsonableError(_("We were unable to render your updated message"))

    do_update_message(user_profile, message, subject, propagate_mode, content, rendered_content)
    return json_success()
Example #7
0
    def test_send_message_events(self):
        schema_checker = check_dict([
            ('type', equals('message')),
            ('flags', check_list(None)),
            ('message',
             check_dict([
                 ('avatar_url', check_string),
                 ('client', check_string),
                 ('content', check_string),
                 ('content_type', equals('text/html')),
                 ('display_recipient', check_string),
                 ('gravatar_hash', check_string),
                 ('id', check_int),
                 ('recipient_id', check_int),
                 ('sender_domain', check_string),
                 ('sender_email', check_string),
                 ('sender_full_name', check_string),
                 ('sender_id', check_int),
                 ('sender_short_name', check_string),
                 ('subject', check_string),
                 ('subject_links', check_list(None)),
                 ('timestamp', check_int),
                 ('type', check_string),
             ])),
        ])
        events = self.do_test(lambda: self.send_message(
            "*****@*****.**", "Verona", Recipient.STREAM, "hello"))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)

        schema_checker = check_dict([
            ('type', equals('update_message')),
            ('flags', check_list(None)),
            ('content', check_string),
            ('edit_timestamp', check_int),
            ('flags', check_list(None)),
            ('message_id', check_int),
            ('message_ids', check_list(check_int)),
            ('orig_content', check_string),
            ('orig_rendered_content', check_string),
            ('orig_subject', check_string),
            ('propagate_mode', check_string),
            ('rendered_content', check_string),
            ('sender', check_string),
            ('stream_id', check_int),
            ('subject', check_string),
            ('subject_links', check_list(None)),
            # There is also a timestamp field in the event, but we ignore it, as
            # it's kind of an unwanted but harmless side effect of calling log_event.
        ])

        message_id = Message.objects.order_by('-id')[0].id
        topic = 'new_topic'
        propagate_mode = 'change_all'
        content = 'new content'
        events = self.do_test(lambda: do_update_message(
            self.user_profile, message_id, topic, propagate_mode, content))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)
Example #8
0
    def test_send_message_events(self):
        schema_checker = check_dict([
            ('type', equals('message')),
            ('flags', check_list(None)),
            ('message', check_dict([
                ('avatar_url', check_string),
                ('client', check_string),
                ('content', check_string),
                ('content_type', equals('text/html')),
                ('display_recipient', check_string),
                ('gravatar_hash', check_string),
                ('id', check_int),
                ('recipient_id', check_int),
                ('sender_domain', check_string),
                ('sender_email', check_string),
                ('sender_full_name', check_string),
                ('sender_id', check_int),
                ('sender_short_name', check_string),
                ('subject', check_string),
                ('subject_links', check_list(None)),
                ('timestamp', check_int),
                ('type', check_string),
            ])),
        ])
        events = self.do_test(lambda: self.send_message("*****@*****.**", "Verona", Recipient.STREAM, "hello"))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)

        schema_checker = check_dict([
            ('type', equals('update_message')),
            ('flags', check_list(None)),
            ('content', check_string),
            ('edit_timestamp', check_int),
            ('flags', check_list(None)),
            ('message_id', check_int),
            ('message_ids', check_list(check_int)),
            ('orig_content', check_string),
            ('orig_rendered_content', check_string),
            ('orig_subject', check_string),
            ('propagate_mode', check_string),
            ('rendered_content', check_string),
            ('sender', check_string),
            ('stream_id', check_int),
            ('subject', check_string),
            ('subject_links', check_list(None)),
            # There is also a timestamp field in the event, but we ignore it, as
            # it's kind of an unwanted but harmless side effect of calling log_event.
        ])

        message_id = Message.objects.order_by('-id')[0].id
        topic = 'new_topic'
        propagate_mode = 'change_all'
        content = 'new content'
        events = self.do_test(lambda: do_update_message(self.user_profile, message_id, topic, propagate_mode, content))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)
Example #9
0
def update_message_backend(request, user_profile,
                           message_id=REQ(converter=to_non_negative_int),
                           subject=REQ(default=None),
                           propagate_mode=REQ(default="change_one"),
                           content=REQ(default=None)):
    # type: (HttpRequest, UserProfile, int, Optional[text_type], Optional[str], Optional[text_type]) -> HttpResponse
    if not user_profile.realm.allow_message_editing:
        return json_error(_("Your organization has turned off message editing."))

    if subject is None and content is None:
        return json_error(_("Nothing to change"))
    if subject is not None:
        subject = subject.strip()
        if subject == "":
            raise JsonableError(_("Topic can't be empty"))
    if content is not None:
        content = content.strip()
        if content == "":
            raise JsonableError(_("Content can't be empty"))

    do_update_message(user_profile, message_id, subject, propagate_mode, content)
    return json_success()
Example #10
0
def update_message_backend(
    request: HttpRequest,
    user_profile: UserMessage,
    message_id: int = REQ(converter=to_non_negative_int, path_only=True),
    stream_id: Optional[int] = REQ(converter=to_non_negative_int,
                                   default=None),
    topic_name: Optional[str] = REQ_topic(),
    propagate_mode: Optional[str] = REQ(
        default="change_one",
        str_validator=check_string_in(PROPAGATE_MODE_VALUES)),
    send_notification_to_old_thread: bool = REQ(default=True,
                                                validator=check_bool),
    send_notification_to_new_thread: bool = REQ(default=True,
                                                validator=check_bool),
    content: Optional[str] = REQ(default=None)
) -> HttpResponse:
    if not user_profile.realm.allow_message_editing:
        return json_error(
            _("Your organization has turned off message editing"))

    if propagate_mode != "change_one" and topic_name is None and stream_id is None:
        return json_error(_("Invalid propagate_mode without topic edit"))

    message, ignored_user_message = access_message(user_profile, message_id)
    is_no_topic_msg = (message.topic_name() == "(no topic)")

    # You only have permission to edit a message if:
    # you change this value also change those two parameters in message_edit.js.
    # 1. You sent it, OR:
    # 2. This is a topic-only edit for a (no topic) message, OR:
    # 3. This is a topic-only edit and you are an admin, OR:
    # 4. This is a topic-only edit and your realm allows users to edit topics.
    if message.sender == user_profile:
        pass
    elif (content is
          None) and (is_no_topic_msg or user_profile.is_realm_admin
                     or user_profile.realm.allow_community_topic_editing):
        pass
    else:
        raise JsonableError(
            _("You don't have permission to edit this message"))

    # If there is a change to the content, check that it hasn't been too long
    # Allow an extra 20 seconds since we potentially allow editing 15 seconds
    # past the limit, and in case there are network issues, etc. The 15 comes
    # from (min_seconds_to_edit + seconds_left_buffer) in message_edit.js; if
    # you change this value also change those two parameters in message_edit.js.
    edit_limit_buffer = 20
    if content is not None and user_profile.realm.message_content_edit_limit_seconds > 0:
        deadline_seconds = user_profile.realm.message_content_edit_limit_seconds + edit_limit_buffer
        if (timezone_now() - message.date_sent) > datetime.timedelta(
                seconds=deadline_seconds):
            raise JsonableError(
                _("The time limit for editing this message has passed"))

    # If there is a change to the topic, check that the user is allowed to
    # edit it and that it has not been too long. If this is not the user who
    # sent the message, they are not the admin, and the time limit for editing
    # topics is passed, raise an error.
    if content is None and message.sender != user_profile and not user_profile.is_realm_admin and \
            not is_no_topic_msg:
        deadline_seconds = Realm.DEFAULT_COMMUNITY_TOPIC_EDITING_LIMIT_SECONDS + edit_limit_buffer
        if (timezone_now() - message.date_sent) > datetime.timedelta(
                seconds=deadline_seconds):
            raise JsonableError(
                _("The time limit for editing this message has passed"))

    if topic_name is None and content is None and stream_id is None:
        return json_error(_("Nothing to change"))
    if topic_name is not None:
        topic_name = topic_name.strip()
        if topic_name == "":
            raise JsonableError(_("Topic can't be empty"))
    rendered_content = None
    links_for_embed: Set[str] = set()
    prior_mention_user_ids: Set[int] = set()
    mention_user_ids: Set[int] = set()
    mention_data: Optional[bugdown.MentionData] = None
    if content is not None:
        content = content.strip()
        if content == "":
            content = "(deleted)"
        content = truncate_body(content)

        mention_data = bugdown.MentionData(
            realm_id=user_profile.realm.id,
            content=content,
        )
        user_info = get_user_info_for_message_updates(message.id)
        prior_mention_user_ids = user_info['mention_user_ids']

        # We render the message using the current user's realm; since
        # the cross-realm bots never edit messages, this should be
        # always correct.
        # Note: If rendering fails, the called code will raise a JsonableError.
        rendered_content = render_incoming_message(
            message,
            content,
            user_info['message_user_ids'],
            user_profile.realm,
            mention_data=mention_data)
        links_for_embed |= message.links_for_preview

        mention_user_ids = message.mentions_user_ids

    new_stream = None
    old_stream = None
    number_changed = 0

    if stream_id is not None:
        if not user_profile.is_realm_admin:
            raise JsonableError(
                _("You don't have permission to move this message"))
        if content is not None:
            raise JsonableError(
                _("Cannot change message content while changing stream"))

        old_stream = get_stream_by_id(message.recipient.type_id)
        new_stream = get_stream_by_id(stream_id)

        if not (old_stream.is_public() and new_stream.is_public()):
            # We'll likely decide to relax this condition in the
            # future; it just requires more care with details like the
            # breadcrumb messages.
            raise JsonableError(_("Streams must be public"))

    number_changed = do_update_message(
        user_profile, message, new_stream, topic_name, propagate_mode,
        send_notification_to_old_thread, send_notification_to_new_thread,
        content, rendered_content, prior_mention_user_ids, mention_user_ids,
        mention_data)

    # Include the number of messages changed in the logs
    request._log_data['extra'] = f"[{number_changed}]"
    if links_for_embed:
        event_data = {
            'message_id': message.id,
            'message_content': message.content,
            # The choice of `user_profile.realm_id` rather than
            # `sender.realm_id` must match the decision made in the
            # `render_incoming_message` call earlier in this function.
            'message_realm_id': user_profile.realm_id,
            'urls': links_for_embed
        }
        queue_json_publish('embed_links', event_data)
    return json_success()
Example #11
0
def update_message_backend(request, user_profile,
                           message_id=REQ(converter=to_non_negative_int),
                           subject=REQ(default=None),
                           propagate_mode=REQ(default="change_one"),
                           content=REQ(default=None)):
    # type: (HttpRequest, UserProfile, int, Optional[Text], Optional[str], Optional[Text]) -> HttpResponse
    if not user_profile.realm.allow_message_editing:
        return json_error(_("Your organization has turned off message editing."))

    message, ignored_user_message = access_message(user_profile, message_id)

    # You only have permission to edit a message if:
    # 1. You sent it, OR:
    # 2. This is a topic-only edit for a (no topic) message, OR:
    # 3. This is a topic-only edit and you are an admin.
    if message.sender == user_profile:
        pass
    elif (content is None) and ((message.topic_name() == "(no topic)") or
                                user_profile.is_realm_admin):
        pass
    else:
        raise JsonableError(_("You don't have permission to edit this message"))

    # If there is a change to the content, check that it hasn't been too long
    # Allow an extra 20 seconds since we potentially allow editing 15 seconds
    # past the limit, and in case there are network issues, etc. The 15 comes
    # from (min_seconds_to_edit + seconds_left_buffer) in message_edit.js; if
    # you change this value also change those two parameters in message_edit.js.
    edit_limit_buffer = 20
    if content is not None and user_profile.realm.message_content_edit_limit_seconds > 0:
        deadline_seconds = user_profile.realm.message_content_edit_limit_seconds + edit_limit_buffer
        if (timezone.now() - message.pub_date) > datetime.timedelta(seconds=deadline_seconds):
            raise JsonableError(_("The time limit for editing this message has past"))

    if subject is None and content is None:
        return json_error(_("Nothing to change"))
    if subject is not None:
        subject = subject.strip()
        if subject == "":
            raise JsonableError(_("Topic can't be empty"))
    rendered_content = None
    links_for_embed = set()  # type: Set[Text]
    if content is not None:
        content = content.strip()
        if content == "":
            content = "(deleted)"
        content = truncate_body(content)

        # We exclude UserMessage.flags.historical rows since those
        # users did not receive the message originally, and thus
        # probably are not relevant for reprocessed alert_words,
        # mentions and similar rendering features.  This may be a
        # decision we change in the future.
        ums = UserMessage.objects.filter(
            message=message.id,
            flags=~UserMessage.flags.historical)

        message_users = UserProfile.objects.select_related().filter(
            id__in={um.user_profile_id for um in ums})

        # We render the message using the current user's realm; since
        # the cross-realm bots never edit messages, this should be
        # always correct.
        # Note: If rendering fails, the called code will raise a JsonableError.
        rendered_content = render_incoming_message(message,
                                                   content,
                                                   message_users,
                                                   user_profile.realm)
        links_for_embed |= message.links_for_preview

    number_changed = do_update_message(user_profile, message, subject,
                                       propagate_mode, content, rendered_content)
    # Include the number of messages changed in the logs
    request._log_data['extra'] = "[%s]" % (number_changed,)
    if links_for_embed and getattr(settings, 'INLINE_URL_EMBED_PREVIEW', None):
        event_data = {
            'message_id': message.id,
            'message_content': message.content,
            # The choice of `user_profile.realm_id` rather than
            # `sender.realm_id` must match the decision made in the
            # `render_incoming_message` call earlier in this function.
            'message_realm_id': user_profile.realm_id,
            'urls': links_for_embed}
        queue_json_publish('embed_links', event_data, lambda x: None)
    return json_success()
Example #12
0
def update_message_backend(
    request,
    user_profile,
    message_id=REQ(converter=to_non_negative_int),
    subject=REQ(default=None),
    propagate_mode=REQ(default="change_one"),
    content=REQ(default=None),
):
    # type: (HttpRequest, UserProfile, int, Optional[text_type], Optional[str], Optional[text_type]) -> HttpResponse
    if not user_profile.realm.allow_message_editing:
        return json_error(_("Your organization has turned off message editing."))

    try:
        message = Message.objects.select_related().get(id=message_id)
    except Message.DoesNotExist:
        raise JsonableError(_("Unknown message id"))

    # You only have permission to edit a message if:
    # 1. You sent it, OR:
    # 2. This is a topic-only edit for a (no topic) message, OR:
    # 3. This is a topic-only edit and you are an admin.
    if message.sender == user_profile:
        pass
    elif (content is None) and ((message.topic_name() == "(no topic)") or user_profile.is_realm_admin):
        pass
    else:
        raise JsonableError(_("You don't have permission to edit this message"))

    # If there is a change to the content, check that it hasn't been too long
    # Allow an extra 20 seconds since we potentially allow editing 15 seconds
    # past the limit, and in case there are network issues, etc. The 15 comes
    # from (min_seconds_to_edit + seconds_left_buffer) in message_edit.js; if
    # you change this value also change those two parameters in message_edit.js.
    edit_limit_buffer = 20
    if content is not None and user_profile.realm.message_content_edit_limit_seconds > 0:
        deadline_seconds = user_profile.realm.message_content_edit_limit_seconds + edit_limit_buffer
        if (now() - message.pub_date) > datetime.timedelta(seconds=deadline_seconds):
            raise JsonableError(_("The time limit for editing this message has past"))

    if subject is None and content is None:
        return json_error(_("Nothing to change"))
    if subject is not None:
        subject = subject.strip()
        if subject == "":
            raise JsonableError(_("Topic can't be empty"))
    rendered_content = None
    if content is not None:
        content = content.strip()
        if content == "":
            raise JsonableError(_("Content can't be empty"))
        content = truncate_body(content)

        # We exclude UserMessage.flags.historical rows since those
        # users did not receive the message originally, and thus
        # probably are not relevant for reprocessed alert_words,
        # mentions and similar rendering features.  This may be a
        # decision we change in the future.
        ums = UserMessage.objects.filter(message=message.id, flags=~UserMessage.flags.historical)
        message_users = {get_user_profile_by_id(um.user_profile_id) for um in ums}
        # If rendering fails, the called code will raise a JsonableError.
        rendered_content = render_incoming_message(message, content=content, message_users=message_users)

    do_update_message(user_profile, message, subject, propagate_mode, content, rendered_content)
    return json_success()
Example #13
0
def update_message_backend(request,
                           user_profile,
                           message_id=REQ(converter=to_non_negative_int),
                           subject=REQ(default=None),
                           propagate_mode=REQ(default="change_one"),
                           content=REQ(default=None)):
    # type: (HttpRequest, UserProfile, int, Optional[text_type], Optional[str], Optional[text_type]) -> HttpResponse
    if not user_profile.realm.allow_message_editing:
        return json_error(
            _("Your organization has turned off message editing."))

    try:
        message = Message.objects.select_related().get(id=message_id)
    except Message.DoesNotExist:
        raise JsonableError(_("Unknown message id"))

    # You only have permission to edit a message if:
    # 1. You sent it, OR:
    # 2. This is a topic-only edit for a (no topic) message, OR:
    # 3. This is a topic-only edit and you are an admin.
    if message.sender == user_profile:
        pass
    elif (content is None) and ((message.topic_name() == "(no topic)")
                                or user_profile.is_realm_admin):
        pass
    else:
        raise JsonableError(
            _("You don't have permission to edit this message"))

    # If there is a change to the content, check that it hasn't been too long
    # Allow an extra 20 seconds since we potentially allow editing 15 seconds
    # past the limit, and in case there are network issues, etc. The 15 comes
    # from (min_seconds_to_edit + seconds_left_buffer) in message_edit.js; if
    # you change this value also change those two parameters in message_edit.js.
    edit_limit_buffer = 20
    if content is not None and user_profile.realm.message_content_edit_limit_seconds > 0:
        deadline_seconds = user_profile.realm.message_content_edit_limit_seconds + edit_limit_buffer
        if (now() - message.pub_date) > datetime.timedelta(
                seconds=deadline_seconds):
            raise JsonableError(
                _("The time limit for editing this message has past"))

    if subject is None and content is None:
        return json_error(_("Nothing to change"))
    if subject is not None:
        subject = subject.strip()
        if subject == "":
            raise JsonableError(_("Topic can't be empty"))
    rendered_content = None
    if content is not None:
        content = content.strip()
        if content == "":
            content = "(deleted)"
        content = truncate_body(content)

        # We exclude UserMessage.flags.historical rows since those
        # users did not receive the message originally, and thus
        # probably are not relevant for reprocessed alert_words,
        # mentions and similar rendering features.  This may be a
        # decision we change in the future.
        ums = UserMessage.objects.filter(message=message.id,
                                         flags=~UserMessage.flags.historical)
        message_users = {
            get_user_profile_by_id(um.user_profile_id)
            for um in ums
        }
        # If rendering fails, the called code will raise a JsonableError.
        rendered_content = render_incoming_message(message,
                                                   content=content,
                                                   message_users=message_users)

    do_update_message(user_profile, message, subject, propagate_mode, content,
                      rendered_content)
    return json_success()