mirror of
https://github.com/zulip/zulip.git
synced 2025-11-01 12:33:40 +00:00
drafts: Extract all of the real work to lib functions.
The main reason why this is needed is because this seems to be convention and because we can't easily test event creation without doing this. Signed-off-by: Hemanth V. Alluri <hdrive1999@gmail.com>
This commit is contained in:
committed by
Tim Abbott
parent
044fe547d3
commit
f1f0a26c37
145
zerver/lib/drafts.py
Normal file
145
zerver/lib/drafts.py
Normal file
@@ -0,0 +1,145 @@
|
||||
import time
|
||||
from functools import wraps
|
||||
from typing import Any, Dict, List, Set, cast
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from zerver.lib.actions import recipient_for_user_profiles
|
||||
from zerver.lib.addressee import get_user_profiles_by_ids
|
||||
from zerver.lib.exceptions import JsonableError, ResourceNotFoundError
|
||||
from zerver.lib.message import normalize_body, truncate_topic
|
||||
from zerver.lib.streams import access_stream_by_id
|
||||
from zerver.lib.timestamp import timestamp_to_datetime
|
||||
from zerver.lib.types import ViewFuncT
|
||||
from zerver.lib.validator import (
|
||||
check_dict_only,
|
||||
check_float,
|
||||
check_int,
|
||||
check_list,
|
||||
check_required_string,
|
||||
check_string,
|
||||
check_string_in,
|
||||
check_union,
|
||||
)
|
||||
from zerver.models import Draft, UserProfile
|
||||
|
||||
VALID_DRAFT_TYPES: Set[str] = {"", "private", "stream"}
|
||||
|
||||
# A validator to verify if the structure (syntax) of a dictionary
|
||||
# meets the requirements to be a draft dictionary:
|
||||
draft_dict_validator = check_dict_only(
|
||||
required_keys=[
|
||||
("type", check_string_in(VALID_DRAFT_TYPES)),
|
||||
("to", check_list(check_int)), # The ID of the stream to send to, or a list of user IDs.
|
||||
("topic", check_string), # This string can simply be empty for private type messages.
|
||||
("content", check_required_string),
|
||||
],
|
||||
optional_keys=[
|
||||
("timestamp", check_union([check_int, check_float])), # A Unix timestamp.
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def further_validated_draft_dict(
|
||||
draft_dict: Dict[str, Any], user_profile: UserProfile
|
||||
) -> Dict[str, Any]:
|
||||
"""Take a draft_dict that was already validated by draft_dict_validator then
|
||||
further sanitize, validate, and transform it. Ultimately return this "further
|
||||
validated" draft dict. It will have a slightly different set of keys the values
|
||||
for which can be used to directly create a Draft object."""
|
||||
|
||||
content = normalize_body(draft_dict["content"])
|
||||
|
||||
timestamp = draft_dict.get("timestamp", time.time())
|
||||
timestamp = round(timestamp, 6)
|
||||
if timestamp < 0:
|
||||
# While it's not exactly an invalid timestamp, it's not something
|
||||
# we want to allow either.
|
||||
raise JsonableError(_("Timestamp must not be negative."))
|
||||
last_edit_time = timestamp_to_datetime(timestamp)
|
||||
|
||||
topic = ""
|
||||
recipient = None
|
||||
to = draft_dict["to"]
|
||||
if draft_dict["type"] == "stream":
|
||||
topic = truncate_topic(draft_dict["topic"])
|
||||
if "\0" in topic:
|
||||
raise JsonableError(_("Topic must not contain null bytes"))
|
||||
if len(to) != 1:
|
||||
raise JsonableError(_("Must specify exactly 1 stream ID for stream messages"))
|
||||
stream, sub = access_stream_by_id(user_profile, to[0])
|
||||
recipient = stream.recipient
|
||||
elif draft_dict["type"] == "private" and len(to) != 0:
|
||||
to_users = get_user_profiles_by_ids(set(to), user_profile.realm)
|
||||
try:
|
||||
recipient = recipient_for_user_profiles(to_users, False, None, user_profile)
|
||||
except ValidationError as e: # nocoverage
|
||||
raise JsonableError(e.messages[0])
|
||||
|
||||
return {
|
||||
"recipient": recipient,
|
||||
"topic": topic,
|
||||
"content": content,
|
||||
"last_edit_time": last_edit_time,
|
||||
}
|
||||
|
||||
|
||||
def draft_endpoint(view_func: ViewFuncT) -> ViewFuncT:
|
||||
@wraps(view_func)
|
||||
def draft_view_func(
|
||||
request: HttpRequest, user_profile: UserProfile, *args: object, **kwargs: object
|
||||
) -> HttpResponse:
|
||||
if not user_profile.enable_drafts_synchronization:
|
||||
raise JsonableError(_("User has disabled synchronizing drafts."))
|
||||
return view_func(request, user_profile, *args, **kwargs)
|
||||
|
||||
return cast(ViewFuncT, draft_view_func) # https://github.com/python/mypy/issues/1927
|
||||
|
||||
|
||||
def do_create_drafts(draft_dicts: List[Dict[str, Any]], user_profile: UserProfile) -> List[Draft]:
|
||||
"""Create drafts in bulk for a given user based on the draft dicts. Since
|
||||
currently, the only place this method is being used (apart from tests) is from
|
||||
the create_draft view, we assume that the drafts_dicts are syntactically valid
|
||||
(i.e. they satisfy the draft_dict_validator)."""
|
||||
draft_objects = []
|
||||
for draft_dict in draft_dicts:
|
||||
valid_draft_dict = further_validated_draft_dict(draft_dict, user_profile)
|
||||
draft_objects.append(
|
||||
Draft(
|
||||
user_profile=user_profile,
|
||||
recipient=valid_draft_dict["recipient"],
|
||||
topic=valid_draft_dict["topic"],
|
||||
content=valid_draft_dict["content"],
|
||||
last_edit_time=valid_draft_dict["last_edit_time"],
|
||||
)
|
||||
)
|
||||
|
||||
created_draft_objects = Draft.objects.bulk_create(draft_objects)
|
||||
return created_draft_objects
|
||||
|
||||
|
||||
def do_edit_draft(draft_id: int, draft_dict: Dict[str, Any], user_profile: UserProfile) -> None:
|
||||
"""Edit/update a single draft for a given user. Since the only place this method is being
|
||||
used from (apart from tests) is the edit_draft view, we assume that the drafts_dict is
|
||||
syntactically valid (i.e. it satisfies the draft_dict_validator)."""
|
||||
try:
|
||||
draft_object = Draft.objects.get(id=draft_id, user_profile=user_profile)
|
||||
except Draft.DoesNotExist:
|
||||
raise ResourceNotFoundError(_("Draft does not exist"))
|
||||
valid_draft_dict = further_validated_draft_dict(draft_dict, user_profile)
|
||||
draft_object.content = valid_draft_dict["content"]
|
||||
draft_object.topic = valid_draft_dict["topic"]
|
||||
draft_object.recipient = valid_draft_dict["recipient"]
|
||||
draft_object.last_edit_time = valid_draft_dict["last_edit_time"]
|
||||
draft_object.save()
|
||||
|
||||
|
||||
def do_delete_draft(draft_id: int, user_profile: UserProfile) -> None:
|
||||
"""Delete a draft belonging to a particular user."""
|
||||
try:
|
||||
draft_object = Draft.objects.get(id=draft_id, user_profile=user_profile)
|
||||
except Draft.DoesNotExist:
|
||||
raise ResourceNotFoundError(_("Draft does not exist"))
|
||||
draft_object.delete()
|
||||
@@ -1,104 +1,19 @@
|
||||
import time
|
||||
from functools import wraps
|
||||
from typing import Any, Dict, List, Set, cast
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from zerver.lib.actions import recipient_for_user_profiles
|
||||
from zerver.lib.addressee import get_user_profiles_by_ids
|
||||
from zerver.lib.exceptions import JsonableError, ResourceNotFoundError
|
||||
from zerver.lib.message import normalize_body, truncate_topic
|
||||
from zerver.lib.drafts import (
|
||||
do_create_drafts,
|
||||
do_delete_draft,
|
||||
do_edit_draft,
|
||||
draft_dict_validator,
|
||||
draft_endpoint,
|
||||
)
|
||||
from zerver.lib.request import REQ, has_request_variables
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.streams import access_stream_by_id
|
||||
from zerver.lib.timestamp import timestamp_to_datetime
|
||||
from zerver.lib.types import ViewFuncT
|
||||
from zerver.lib.validator import (
|
||||
check_dict_only,
|
||||
check_float,
|
||||
check_int,
|
||||
check_list,
|
||||
check_required_string,
|
||||
check_string,
|
||||
check_string_in,
|
||||
check_union,
|
||||
)
|
||||
from zerver.lib.validator import check_list
|
||||
from zerver.models import Draft, UserProfile
|
||||
|
||||
VALID_DRAFT_TYPES: Set[str] = {"", "private", "stream"}
|
||||
|
||||
# A validator to verify if the structure (syntax) of a dictionary
|
||||
# meets the requirements to be a draft dictionary:
|
||||
draft_dict_validator = check_dict_only(
|
||||
required_keys=[
|
||||
("type", check_string_in(VALID_DRAFT_TYPES)),
|
||||
("to", check_list(check_int)), # The ID of the stream to send to, or a list of user IDs.
|
||||
("topic", check_string), # This string can simply be empty for private type messages.
|
||||
("content", check_required_string),
|
||||
],
|
||||
optional_keys=[
|
||||
("timestamp", check_union([check_int, check_float])), # A Unix timestamp.
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def further_validated_draft_dict(
|
||||
draft_dict: Dict[str, Any], user_profile: UserProfile
|
||||
) -> Dict[str, Any]:
|
||||
"""Take a draft_dict that was already validated by draft_dict_validator then
|
||||
further sanitize, validate, and transform it. Ultimately return this "further
|
||||
validated" draft dict. It will have a slightly different set of keys the values
|
||||
for which can be used to directly create a Draft object."""
|
||||
|
||||
content = normalize_body(draft_dict["content"])
|
||||
|
||||
timestamp = draft_dict.get("timestamp", time.time())
|
||||
timestamp = round(timestamp, 6)
|
||||
if timestamp < 0:
|
||||
# While it's not exactly an invalid timestamp, it's not something
|
||||
# we want to allow either.
|
||||
raise JsonableError(_("Timestamp must not be negative."))
|
||||
last_edit_time = timestamp_to_datetime(timestamp)
|
||||
|
||||
topic = ""
|
||||
recipient = None
|
||||
to = draft_dict["to"]
|
||||
if draft_dict["type"] == "stream":
|
||||
topic = truncate_topic(draft_dict["topic"])
|
||||
if "\x00" in topic:
|
||||
raise JsonableError(_("Topic must not contain null bytes"))
|
||||
if len(to) != 1:
|
||||
raise JsonableError(_("Must specify exactly 1 stream ID for stream messages"))
|
||||
stream, sub = access_stream_by_id(user_profile, to[0])
|
||||
recipient = stream.recipient
|
||||
elif draft_dict["type"] == "private" and len(to) != 0:
|
||||
to_users = get_user_profiles_by_ids(set(to), user_profile.realm)
|
||||
try:
|
||||
recipient = recipient_for_user_profiles(to_users, False, None, user_profile)
|
||||
except ValidationError as e: # nocoverage
|
||||
raise JsonableError(e.messages[0])
|
||||
|
||||
return {
|
||||
"recipient": recipient,
|
||||
"topic": topic,
|
||||
"content": content,
|
||||
"last_edit_time": last_edit_time,
|
||||
}
|
||||
|
||||
|
||||
def draft_endpoint(view_func: ViewFuncT) -> ViewFuncT:
|
||||
@wraps(view_func)
|
||||
def draft_view_func(
|
||||
request: HttpRequest, user_profile: UserProfile, *args: object, **kwargs: object
|
||||
) -> HttpResponse:
|
||||
if not user_profile.enable_drafts_synchronization:
|
||||
raise JsonableError(_("User has disabled synchronizing drafts."))
|
||||
return view_func(request, user_profile, *args, **kwargs)
|
||||
|
||||
return cast(ViewFuncT, draft_view_func) # https://github.com/python/mypy/issues/1927
|
||||
|
||||
|
||||
@draft_endpoint
|
||||
def fetch_drafts(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
||||
@@ -116,20 +31,7 @@ def create_drafts(
|
||||
"drafts", json_validator=check_list(draft_dict_validator)
|
||||
),
|
||||
) -> HttpResponse:
|
||||
draft_objects = []
|
||||
for draft_dict in draft_dicts:
|
||||
valid_draft_dict = further_validated_draft_dict(draft_dict, user_profile)
|
||||
draft_objects.append(
|
||||
Draft(
|
||||
user_profile=user_profile,
|
||||
recipient=valid_draft_dict["recipient"],
|
||||
topic=valid_draft_dict["topic"],
|
||||
content=valid_draft_dict["content"],
|
||||
last_edit_time=valid_draft_dict["last_edit_time"],
|
||||
)
|
||||
)
|
||||
|
||||
created_draft_objects = Draft.objects.bulk_create(draft_objects)
|
||||
created_draft_objects = do_create_drafts(draft_dicts, user_profile)
|
||||
draft_ids = [draft_object.id for draft_object in created_draft_objects]
|
||||
return json_success({"ids": draft_ids})
|
||||
|
||||
@@ -142,27 +44,11 @@ def edit_draft(
|
||||
draft_id: int,
|
||||
draft_dict: Dict[str, Any] = REQ("draft", json_validator=draft_dict_validator),
|
||||
) -> HttpResponse:
|
||||
try:
|
||||
draft_object = Draft.objects.get(id=draft_id, user_profile=user_profile)
|
||||
except Draft.DoesNotExist:
|
||||
raise ResourceNotFoundError(_("Draft does not exist"))
|
||||
|
||||
valid_draft_dict = further_validated_draft_dict(draft_dict, user_profile)
|
||||
draft_object.content = valid_draft_dict["content"]
|
||||
draft_object.topic = valid_draft_dict["topic"]
|
||||
draft_object.recipient = valid_draft_dict["recipient"]
|
||||
draft_object.last_edit_time = valid_draft_dict["last_edit_time"]
|
||||
draft_object.save()
|
||||
|
||||
do_edit_draft(draft_id, draft_dict, user_profile)
|
||||
return json_success()
|
||||
|
||||
|
||||
@draft_endpoint
|
||||
def delete_draft(request: HttpRequest, user_profile: UserProfile, draft_id: int) -> HttpResponse:
|
||||
try:
|
||||
draft_object = Draft.objects.get(id=draft_id, user_profile=user_profile)
|
||||
except Draft.DoesNotExist:
|
||||
raise ResourceNotFoundError(_("Draft does not exist"))
|
||||
|
||||
draft_object.delete()
|
||||
do_delete_draft(draft_id, user_profile)
|
||||
return json_success()
|
||||
|
||||
Reference in New Issue
Block a user