views: Extract message_send.py for sending views.

This commit is contained in:
Tim Abbott 2020-06-22 14:25:37 -07:00 committed by Tim Abbott
parent 55448ecf24
commit 3657717ade
4 changed files with 312 additions and 296 deletions

View File

@ -114,7 +114,7 @@ from zerver.models import (
get_system_bot,
get_user,
)
from zerver.views.messages import InvalidMirrorInput, create_mirrored_message_users
from zerver.views.message_send import InvalidMirrorInput, create_mirrored_message_users
class MiscMessageTest(ZulipTestCase):
@ -2233,7 +2233,7 @@ class MessagePOSTTest(ZulipTestCase):
subdomain="zephyr")
self.assert_json_error(result, "User not authorized for this query")
@mock.patch("zerver.views.messages.create_mirrored_message_users")
@mock.patch("zerver.views.message_send.create_mirrored_message_users")
def test_send_message_create_mirrored_message_user_returns_invalid_input(
self, create_mirrored_message_users_mock: Any) -> None:
create_mirrored_message_users_mock.side_effect = InvalidMirrorInput()
@ -2246,7 +2246,7 @@ class MessagePOSTTest(ZulipTestCase):
subdomain="zephyr")
self.assert_json_error(result, "Invalid mirrored message")
@mock.patch("zerver.views.messages.create_mirrored_message_users")
@mock.patch("zerver.views.message_send.create_mirrored_message_users")
def test_send_message_when_client_is_zephyr_mirror_but_string_id_is_not_zephyr(
self, create_mirrored_message_users_mock: Any) -> None:
create_mirrored_message_users_mock.return_value = mock.Mock()
@ -2262,7 +2262,7 @@ class MessagePOSTTest(ZulipTestCase):
subdomain="notzephyr")
self.assert_json_error(result, "Zephyr mirroring is not allowed in this organization")
@mock.patch("zerver.views.messages.create_mirrored_message_users")
@mock.patch("zerver.views.message_send.create_mirrored_message_users")
def test_send_message_when_client_is_zephyr_mirror_but_recipient_is_user_id(
self, create_mirrored_message_users_mock: Any) -> None:
create_mirrored_message_users_mock.return_value = mock.Mock()

View File

@ -0,0 +1,300 @@
from typing import Iterable, Optional, Sequence, Union, cast
from dateutil.parser import parse as dateparser
from django.core import validators
from django.core.exceptions import ValidationError
from django.http import HttpRequest, HttpResponse
from django.utils.timezone import now as timezone_now
from django.utils.translation import ugettext as _
from zerver.decorator import REQ, has_request_variables
from zerver.lib.actions import (
check_schedule_message,
check_send_message,
compute_irc_user_fullname,
compute_jabber_user_fullname,
create_mirror_user_if_needed,
extract_private_recipients,
extract_stream_indicator,
)
from zerver.lib.message import render_markdown
from zerver.lib.response import json_error, json_success
from zerver.lib.timestamp import convert_to_UTC
from zerver.lib.timezone import get_timezone
from zerver.lib.topic import REQ_topic
from zerver.lib.zcommand import process_zcommands
from zerver.lib.zephyr import compute_mit_user_fullname
from zerver.models import (
Client,
Message,
Realm,
RealmDomain,
UserProfile,
email_to_domain,
get_realm,
get_user_including_cross_realm,
)
class InvalidMirrorInput(Exception):
pass
def create_mirrored_message_users(request: HttpRequest, user_profile: UserProfile,
recipients: Iterable[str]) -> UserProfile:
if "sender" not in request.POST:
raise InvalidMirrorInput("No sender")
sender_email = request.POST["sender"].strip().lower()
referenced_users = {sender_email}
if request.POST['type'] == 'private':
for email in recipients:
referenced_users.add(email.lower())
if request.client.name == "zephyr_mirror":
user_check = same_realm_zephyr_user
fullname_function = compute_mit_user_fullname
elif request.client.name == "irc_mirror":
user_check = same_realm_irc_user
fullname_function = compute_irc_user_fullname
elif request.client.name in ("jabber_mirror", "JabberMirror"):
user_check = same_realm_jabber_user
fullname_function = compute_jabber_user_fullname
else:
raise InvalidMirrorInput("Unrecognized mirroring client")
for email in referenced_users:
# Check that all referenced users are in our realm:
if not user_check(user_profile, email):
raise InvalidMirrorInput("At least one user cannot be mirrored")
# Create users for the referenced users, if needed.
for email in referenced_users:
create_mirror_user_if_needed(user_profile.realm, email, fullname_function)
sender = get_user_including_cross_realm(sender_email, user_profile.realm)
return sender
def same_realm_zephyr_user(user_profile: UserProfile, email: str) -> bool:
#
# Are the sender and recipient both addresses in the same Zephyr
# mirroring realm? We have to handle this specially, inferring
# the domain from the e-mail address, because the recipient may
# not existing in Zulip and we may need to make a stub Zephyr
# mirroring user on the fly.
try:
validators.validate_email(email)
except ValidationError:
return False
domain = email_to_domain(email)
# Assumes allow_subdomains=False for all RealmDomain's corresponding to
# these realms.
return user_profile.realm.is_zephyr_mirror_realm and \
RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists()
def same_realm_irc_user(user_profile: UserProfile, email: str) -> bool:
# Check whether the target email address is an IRC user in the
# same realm as user_profile, i.e. if the domain were example.com,
# the IRC user would need to be username@irc.example.com
try:
validators.validate_email(email)
except ValidationError:
return False
domain = email_to_domain(email).replace("irc.", "")
# Assumes allow_subdomains=False for all RealmDomain's corresponding to
# these realms.
return RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists()
def same_realm_jabber_user(user_profile: UserProfile, email: str) -> bool:
try:
validators.validate_email(email)
except ValidationError:
return False
# If your Jabber users have a different email domain than the
# Zulip users, this is where you would do any translation.
domain = email_to_domain(email)
# Assumes allow_subdomains=False for all RealmDomain's corresponding to
# these realms.
return RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists()
def handle_deferred_message(sender: UserProfile, client: Client,
message_type_name: str,
message_to: Union[Sequence[str], Sequence[int]],
topic_name: Optional[str],
message_content: str, delivery_type: str,
defer_until: str, tz_guess: Optional[str],
forwarder_user_profile: UserProfile,
realm: Optional[Realm]) -> HttpResponse:
deliver_at = None
local_tz = 'UTC'
if tz_guess:
local_tz = tz_guess
elif sender.timezone:
local_tz = sender.timezone
try:
deliver_at = dateparser(defer_until)
except ValueError:
return json_error(_("Invalid time format"))
deliver_at_usertz = deliver_at
if deliver_at_usertz.tzinfo is None:
user_tz = get_timezone(local_tz)
# Since mypy is not able to recognize localize and normalize as attributes of tzinfo we use ignore.
deliver_at_usertz = user_tz.normalize(user_tz.localize(deliver_at)) # type: ignore[attr-defined] # Reason in comment on previous line.
deliver_at = convert_to_UTC(deliver_at_usertz)
if deliver_at <= timezone_now():
return json_error(_("Time must be in the future."))
check_schedule_message(sender, client, message_type_name, message_to,
topic_name, message_content, delivery_type,
deliver_at, realm=realm,
forwarder_user_profile=forwarder_user_profile)
return json_success({"deliver_at": str(deliver_at_usertz)})
@has_request_variables
def send_message_backend(request: HttpRequest, user_profile: UserProfile,
message_type_name: str=REQ('type'),
req_to: Optional[str]=REQ('to', default=None),
forged_str: Optional[str]=REQ("forged",
default=None,
documentation_pending=True),
topic_name: Optional[str]=REQ_topic(),
message_content: str=REQ('content'),
widget_content: Optional[str]=REQ(default=None,
documentation_pending=True),
realm_str: Optional[str]=REQ('realm_str', default=None,
documentation_pending=True),
local_id: Optional[str]=REQ(default=None,
documentation_pending=True),
queue_id: Optional[str]=REQ(default=None,
documentation_pending=True),
delivery_type: str=REQ('delivery_type', default='send_now',
documentation_pending=True),
defer_until: Optional[str]=REQ('deliver_at', default=None,
documentation_pending=True),
tz_guess: Optional[str]=REQ('tz_guess', default=None,
documentation_pending=True),
) -> HttpResponse:
# If req_to is None, then we default to an
# empty list of recipients.
message_to: Union[Sequence[int], Sequence[str]] = []
if req_to is not None:
if message_type_name == 'stream':
stream_indicator = extract_stream_indicator(req_to)
# For legacy reasons check_send_message expects
# a list of streams, instead of a single stream.
#
# Also, mypy can't detect that a single-item
# list populated from a Union[int, str] is actually
# a Union[Sequence[int], Sequence[str]].
message_to = cast(
Union[Sequence[int], Sequence[str]],
[stream_indicator],
)
else:
message_to = extract_private_recipients(req_to)
# Temporary hack: We're transitioning `forged` from accepting
# `yes` to accepting `true` like all of our normal booleans.
forged = forged_str is not None and forged_str in ["yes", "true"]
client = request.client
is_super_user = request.user.is_api_super_user
if forged and not is_super_user:
return json_error(_("User not authorized for this query"))
realm = None
if realm_str and realm_str != user_profile.realm.string_id:
if not is_super_user:
# The email gateway bot needs to be able to send messages in
# any realm.
return json_error(_("User not authorized for this query"))
try:
realm = get_realm(realm_str)
except Realm.DoesNotExist:
return json_error(_("Unknown organization '{}'").format(realm_str))
if client.name in ["zephyr_mirror", "irc_mirror", "jabber_mirror", "JabberMirror"]:
# Here's how security works for mirroring:
#
# For private messages, the message must be (1) both sent and
# received exclusively by users in your realm, and (2)
# received by the forwarding user.
#
# For stream messages, the message must be (1) being forwarded
# by an API superuser for your realm and (2) being sent to a
# mirrored stream.
#
# The most important security checks are in
# `create_mirrored_message_users` below, which checks the
# same-realm constraint.
if "sender" not in request.POST:
return json_error(_("Missing sender"))
if message_type_name != "private" and not is_super_user:
return json_error(_("User not authorized for this query"))
# For now, mirroring only works with recipient emails, not for
# recipient user IDs.
if not all(isinstance(to_item, str) for to_item in message_to):
return json_error(_("Mirroring not allowed with recipient user IDs"))
# We need this manual cast so that mypy doesn't complain about
# create_mirrored_message_users not being able to accept a Sequence[int]
# type parameter.
message_to = cast(Sequence[str], message_to)
try:
mirror_sender = create_mirrored_message_users(request, user_profile, message_to)
except InvalidMirrorInput:
return json_error(_("Invalid mirrored message"))
if client.name == "zephyr_mirror" and not user_profile.realm.is_zephyr_mirror_realm:
return json_error(_("Zephyr mirroring is not allowed in this organization"))
sender = mirror_sender
else:
if "sender" in request.POST:
return json_error(_("Invalid mirrored message"))
sender = user_profile
if (delivery_type == 'send_later' or delivery_type == 'remind') and defer_until is None:
return json_error(_("Missing deliver_at in a request for delayed message delivery"))
if (delivery_type == 'send_later' or delivery_type == 'remind') and defer_until is not None:
return handle_deferred_message(sender, client, message_type_name,
message_to, topic_name, message_content,
delivery_type, defer_until, tz_guess,
forwarder_user_profile=user_profile,
realm=realm)
ret = check_send_message(sender, client, message_type_name, message_to,
topic_name, message_content, forged=forged,
forged_timestamp = request.POST.get('time'),
forwarder_user_profile=user_profile, realm=realm,
local_id=local_id, sender_queue_id=queue_id,
widget_content=widget_content)
return json_success({"id": ret})
@has_request_variables
def zcommand_backend(request: HttpRequest, user_profile: UserProfile,
command: str=REQ('command')) -> HttpResponse:
return json_success(process_zcommands(command, user_profile))
@has_request_variables
def render_message_backend(request: HttpRequest, user_profile: UserProfile,
content: str=REQ()) -> HttpResponse:
message = Message()
message.sender = user_profile
message.content = content
message.sending_client = request.client
rendered_content = render_markdown(message, content, realm=user_profile.realm)
return json_success({"rendered": rendered_content})

View File

@ -1,15 +1,12 @@
import re
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union, cast
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
import ujson
from dateutil.parser import parse as dateparser
from django.conf import settings
from django.core import validators
from django.core.exceptions import ValidationError
from django.db import connection
from django.http import HttpRequest, HttpResponse
from django.utils.html import escape as escape_html
from django.utils.timezone import now as timezone_now
from django.utils.translation import ugettext as _
from sqlalchemy import func
from sqlalchemy.dialects import postgresql
@ -30,19 +27,10 @@ from sqlalchemy.sql import (
)
from zerver.decorator import REQ, has_request_variables
from zerver.lib.actions import (
check_schedule_message,
check_send_message,
compute_irc_user_fullname,
compute_jabber_user_fullname,
create_mirror_user_if_needed,
extract_private_recipients,
extract_stream_indicator,
recipient_for_user_profiles,
)
from zerver.lib.actions import recipient_for_user_profiles
from zerver.lib.addressee import get_user_profiles, get_user_profiles_by_ids
from zerver.lib.exceptions import ErrorCode, JsonableError
from zerver.lib.message import get_first_visible_message_id, messages_for_ids, render_markdown
from zerver.lib.message import get_first_visible_message_id, messages_for_ids
from zerver.lib.response import json_error, json_success
from zerver.lib.sqlalchemy_utils import get_sqlalchemy_connection
from zerver.lib.streams import (
@ -51,9 +39,7 @@ from zerver.lib.streams import (
get_public_streams_queryset,
get_stream_by_narrow_operand_access_unchecked,
)
from zerver.lib.timestamp import convert_to_UTC
from zerver.lib.timezone import get_timezone
from zerver.lib.topic import DB_TOPIC_NAME, MATCH_TOPIC, REQ_topic, topic_column_sa, topic_match_sa
from zerver.lib.topic import DB_TOPIC_NAME, MATCH_TOPIC, topic_column_sa, topic_match_sa
from zerver.lib.topic_mutes import exclude_topic_mutes
from zerver.lib.utils import statsd
from zerver.lib.validator import (
@ -67,21 +53,14 @@ from zerver.lib.validator import (
check_string_or_int_list,
to_non_negative_int,
)
from zerver.lib.zcommand import process_zcommands
from zerver.lib.zephyr import compute_mit_user_fullname
from zerver.models import (
Client,
Message,
Realm,
RealmDomain,
Recipient,
Stream,
Subscription,
UserMessage,
UserProfile,
email_to_domain,
get_active_streams,
get_realm,
get_user_by_id_in_realm_including_cross_realm,
get_user_including_cross_realm,
)
@ -770,11 +749,6 @@ def find_first_unread_anchor(sa_conn: Any,
return anchor
@has_request_variables
def zcommand_backend(request: HttpRequest, user_profile: UserProfile,
command: str=REQ('command')) -> HttpResponse:
return json_success(process_zcommands(command, user_profile))
def parse_anchor_value(anchor_val: Optional[str],
use_first_unread_anchor: bool) -> Optional[int]:
"""Given the anchor and use_first_unread_anchor parameters passed by
@ -1131,265 +1105,6 @@ def post_process_limited_query(rows: List[Any],
found_oldest=found_oldest,
history_limited=history_limited,
)
class InvalidMirrorInput(Exception):
pass
def create_mirrored_message_users(request: HttpRequest, user_profile: UserProfile,
recipients: Iterable[str]) -> UserProfile:
if "sender" not in request.POST:
raise InvalidMirrorInput("No sender")
sender_email = request.POST["sender"].strip().lower()
referenced_users = {sender_email}
if request.POST['type'] == 'private':
for email in recipients:
referenced_users.add(email.lower())
if request.client.name == "zephyr_mirror":
user_check = same_realm_zephyr_user
fullname_function = compute_mit_user_fullname
elif request.client.name == "irc_mirror":
user_check = same_realm_irc_user
fullname_function = compute_irc_user_fullname
elif request.client.name in ("jabber_mirror", "JabberMirror"):
user_check = same_realm_jabber_user
fullname_function = compute_jabber_user_fullname
else:
raise InvalidMirrorInput("Unrecognized mirroring client")
for email in referenced_users:
# Check that all referenced users are in our realm:
if not user_check(user_profile, email):
raise InvalidMirrorInput("At least one user cannot be mirrored")
# Create users for the referenced users, if needed.
for email in referenced_users:
create_mirror_user_if_needed(user_profile.realm, email, fullname_function)
sender = get_user_including_cross_realm(sender_email, user_profile.realm)
return sender
def same_realm_zephyr_user(user_profile: UserProfile, email: str) -> bool:
#
# Are the sender and recipient both addresses in the same Zephyr
# mirroring realm? We have to handle this specially, inferring
# the domain from the e-mail address, because the recipient may
# not existing in Zulip and we may need to make a stub Zephyr
# mirroring user on the fly.
try:
validators.validate_email(email)
except ValidationError:
return False
domain = email_to_domain(email)
# Assumes allow_subdomains=False for all RealmDomain's corresponding to
# these realms.
return user_profile.realm.is_zephyr_mirror_realm and \
RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists()
def same_realm_irc_user(user_profile: UserProfile, email: str) -> bool:
# Check whether the target email address is an IRC user in the
# same realm as user_profile, i.e. if the domain were example.com,
# the IRC user would need to be username@irc.example.com
try:
validators.validate_email(email)
except ValidationError:
return False
domain = email_to_domain(email).replace("irc.", "")
# Assumes allow_subdomains=False for all RealmDomain's corresponding to
# these realms.
return RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists()
def same_realm_jabber_user(user_profile: UserProfile, email: str) -> bool:
try:
validators.validate_email(email)
except ValidationError:
return False
# If your Jabber users have a different email domain than the
# Zulip users, this is where you would do any translation.
domain = email_to_domain(email)
# Assumes allow_subdomains=False for all RealmDomain's corresponding to
# these realms.
return RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists()
def handle_deferred_message(sender: UserProfile, client: Client,
message_type_name: str,
message_to: Union[Sequence[str], Sequence[int]],
topic_name: Optional[str],
message_content: str, delivery_type: str,
defer_until: str, tz_guess: Optional[str],
forwarder_user_profile: UserProfile,
realm: Optional[Realm]) -> HttpResponse:
deliver_at = None
local_tz = 'UTC'
if tz_guess:
local_tz = tz_guess
elif sender.timezone:
local_tz = sender.timezone
try:
deliver_at = dateparser(defer_until)
except ValueError:
return json_error(_("Invalid time format"))
deliver_at_usertz = deliver_at
if deliver_at_usertz.tzinfo is None:
user_tz = get_timezone(local_tz)
# Since mypy is not able to recognize localize and normalize as attributes of tzinfo we use ignore.
deliver_at_usertz = user_tz.normalize(user_tz.localize(deliver_at)) # type: ignore[attr-defined] # Reason in comment on previous line.
deliver_at = convert_to_UTC(deliver_at_usertz)
if deliver_at <= timezone_now():
return json_error(_("Time must be in the future."))
check_schedule_message(sender, client, message_type_name, message_to,
topic_name, message_content, delivery_type,
deliver_at, realm=realm,
forwarder_user_profile=forwarder_user_profile)
return json_success({"deliver_at": str(deliver_at_usertz)})
@has_request_variables
def send_message_backend(request: HttpRequest, user_profile: UserProfile,
message_type_name: str=REQ('type'),
req_to: Optional[str]=REQ('to', default=None),
forged_str: Optional[str]=REQ("forged",
default=None,
documentation_pending=True),
topic_name: Optional[str]=REQ_topic(),
message_content: str=REQ('content'),
widget_content: Optional[str]=REQ(default=None,
documentation_pending=True),
realm_str: Optional[str]=REQ('realm_str', default=None,
documentation_pending=True),
local_id: Optional[str]=REQ(default=None,
documentation_pending=True),
queue_id: Optional[str]=REQ(default=None,
documentation_pending=True),
delivery_type: str=REQ('delivery_type', default='send_now',
documentation_pending=True),
defer_until: Optional[str]=REQ('deliver_at', default=None,
documentation_pending=True),
tz_guess: Optional[str]=REQ('tz_guess', default=None,
documentation_pending=True),
) -> HttpResponse:
# If req_to is None, then we default to an
# empty list of recipients.
message_to: Union[Sequence[int], Sequence[str]] = []
if req_to is not None:
if message_type_name == 'stream':
stream_indicator = extract_stream_indicator(req_to)
# For legacy reasons check_send_message expects
# a list of streams, instead of a single stream.
#
# Also, mypy can't detect that a single-item
# list populated from a Union[int, str] is actually
# a Union[Sequence[int], Sequence[str]].
message_to = cast(
Union[Sequence[int], Sequence[str]],
[stream_indicator],
)
else:
message_to = extract_private_recipients(req_to)
# Temporary hack: We're transitioning `forged` from accepting
# `yes` to accepting `true` like all of our normal booleans.
forged = forged_str is not None and forged_str in ["yes", "true"]
client = request.client
is_super_user = request.user.is_api_super_user
if forged and not is_super_user:
return json_error(_("User not authorized for this query"))
realm = None
if realm_str and realm_str != user_profile.realm.string_id:
if not is_super_user:
# The email gateway bot needs to be able to send messages in
# any realm.
return json_error(_("User not authorized for this query"))
try:
realm = get_realm(realm_str)
except Realm.DoesNotExist:
return json_error(_("Unknown organization '{}'").format(realm_str))
if client.name in ["zephyr_mirror", "irc_mirror", "jabber_mirror", "JabberMirror"]:
# Here's how security works for mirroring:
#
# For private messages, the message must be (1) both sent and
# received exclusively by users in your realm, and (2)
# received by the forwarding user.
#
# For stream messages, the message must be (1) being forwarded
# by an API superuser for your realm and (2) being sent to a
# mirrored stream.
#
# The most important security checks are in
# `create_mirrored_message_users` below, which checks the
# same-realm constraint.
if "sender" not in request.POST:
return json_error(_("Missing sender"))
if message_type_name != "private" and not is_super_user:
return json_error(_("User not authorized for this query"))
# For now, mirroring only works with recipient emails, not for
# recipient user IDs.
if not all(isinstance(to_item, str) for to_item in message_to):
return json_error(_("Mirroring not allowed with recipient user IDs"))
# We need this manual cast so that mypy doesn't complain about
# create_mirrored_message_users not being able to accept a Sequence[int]
# type parameter.
message_to = cast(Sequence[str], message_to)
try:
mirror_sender = create_mirrored_message_users(request, user_profile, message_to)
except InvalidMirrorInput:
return json_error(_("Invalid mirrored message"))
if client.name == "zephyr_mirror" and not user_profile.realm.is_zephyr_mirror_realm:
return json_error(_("Zephyr mirroring is not allowed in this organization"))
sender = mirror_sender
else:
if "sender" in request.POST:
return json_error(_("Invalid mirrored message"))
sender = user_profile
if (delivery_type == 'send_later' or delivery_type == 'remind') and defer_until is None:
return json_error(_("Missing deliver_at in a request for delayed message delivery"))
if (delivery_type == 'send_later' or delivery_type == 'remind') and defer_until is not None:
return handle_deferred_message(sender, client, message_type_name,
message_to, topic_name, message_content,
delivery_type, defer_until, tz_guess,
forwarder_user_profile=user_profile,
realm=realm)
ret = check_send_message(sender, client, message_type_name, message_to,
topic_name, message_content, forged=forged,
forged_timestamp = request.POST.get('time'),
forwarder_user_profile=user_profile, realm=realm,
local_id=local_id, sender_queue_id=queue_id,
widget_content=widget_content)
return json_success({"id": ret})
@has_request_variables
def render_message_backend(request: HttpRequest, user_profile: UserProfile,
content: str=REQ()) -> HttpResponse:
message = Message()
message.sender = user_profile
message.content = content
message.sending_client = request.client
rendered_content = render_markdown(message, content, realm=user_profile.realm)
return json_success({"rendered": rendered_content})
@has_request_variables
def messages_in_narrow_backend(request: HttpRequest, user_profile: UserProfile,
msg_ids: List[int]=REQ(validator=check_list(check_int)),

View File

@ -25,6 +25,7 @@ import zerver.views.email_mirror
import zerver.views.home
import zerver.views.message_edit
import zerver.views.message_flags
import zerver.views.message_send
import zerver.views.messages
import zerver.views.muting
import zerver.views.portico
@ -189,20 +190,20 @@ v1_api_and_json_patterns = [
{'POST': 'zerver.views.message_flags.mark_topic_as_read'}),
url(r'^zcommand$', rest_dispatch,
{'POST': 'zerver.views.messages.zcommand_backend'}),
{'POST': 'zerver.views.message_send.zcommand_backend'}),
# messages -> zerver.views.message*
# GET returns messages, possibly filtered, POST sends a message
url(r'^messages$', rest_dispatch,
{'GET': 'zerver.views.messages.get_messages_backend',
'POST': ('zerver.views.messages.send_message_backend',
'POST': ('zerver.views.message_send.send_message_backend',
{'allow_incoming_webhooks'})}),
url(r'^messages/(?P<message_id>[0-9]+)$', rest_dispatch,
{'GET': 'zerver.views.message_edit.json_fetch_raw_message',
'PATCH': 'zerver.views.message_edit.update_message_backend',
'DELETE': 'zerver.views.message_edit.delete_message_backend'}),
url(r'^messages/render$', rest_dispatch,
{'POST': 'zerver.views.messages.render_message_backend'}),
{'POST': 'zerver.views.message_send.render_message_backend'}),
url(r'^messages/flags$', rest_dispatch,
{'POST': 'zerver.views.message_flags.update_message_flags'}),
url(r'^messages/(?P<message_id>\d+)/history$', rest_dispatch,