mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	Add an API endpoint to send typing notification events.
POST to /typing creates a typing event
Required parameters are 'op' ('start' or 'stop') and 'to' (recipient
emails). If there are multiple recipients, the 'to' parameter
should be a JSON string of the list of recipient emails.
The event created looks like:
{
  'type': 'typing',
  'op': 'start',
  'sender': 'hamlet@zulip.com',
  'recipients': [{
    'id': 1,
    'email': 'othello@zulip.com'
  }]
}
			
			
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							bc6421fbfb
						
					
				
				
					commit
					ee97ba04fe
				
			@@ -818,6 +818,72 @@ def do_send_messages(messages):
 | 
				
			|||||||
    # intermingle sending zephyr messages with other messages.
 | 
					    # intermingle sending zephyr messages with other messages.
 | 
				
			||||||
    return already_sent_ids + [message['message'].id for message in messages]
 | 
					    return already_sent_ids + [message['message'].id for message in messages]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_typing_notification_recipients(notification):
 | 
				
			||||||
 | 
					    # type: (Dict[str, Any]) -> Dict[str, Any]
 | 
				
			||||||
 | 
					    if notification['recipient'].type == Recipient.PERSONAL:
 | 
				
			||||||
 | 
					        recipients = [notification['sender'],
 | 
				
			||||||
 | 
					                      get_user_profile_by_id(notification['recipient'].type_id)]
 | 
				
			||||||
 | 
					    elif notification['recipient'].type == Recipient.HUDDLE:
 | 
				
			||||||
 | 
					        # We use select_related()/only() here, while the PERSONAL case above uses
 | 
				
			||||||
 | 
					        # get_user_profile_by_id() to get UserProfile objects from cache.  Streams will
 | 
				
			||||||
 | 
					        # typically have more recipients than PMs, so get_user_profile_by_id() would be
 | 
				
			||||||
 | 
					        # a bit more expensive here, given that we need to hit the DB anyway and only
 | 
				
			||||||
 | 
					        # care about the email from the user profile.
 | 
				
			||||||
 | 
					        fields = [
 | 
				
			||||||
 | 
					            'user_profile__id',
 | 
				
			||||||
 | 
					            'user_profile__email',
 | 
				
			||||||
 | 
					            'user_profile__is_active',
 | 
				
			||||||
 | 
					            'user_profile__realm__domain'
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        query = Subscription.objects.select_related("user_profile", "user_profile__realm").only(*fields).filter(
 | 
				
			||||||
 | 
					                recipient=notification['recipient'], active=True)
 | 
				
			||||||
 | 
					        recipients = [s.user_profile for s in query]
 | 
				
			||||||
 | 
					    elif notification['recipient'].type == Recipient.STREAM:
 | 
				
			||||||
 | 
					        raise ValueError('Forbidden recipient type')
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        raise ValueError('Bad recipient type')
 | 
				
			||||||
 | 
					    notification['recipients'] = [{'id': profile.id, 'email': profile.email} for profile in recipients]
 | 
				
			||||||
 | 
					    notification['active_recipients'] = [profile for profile in recipients if profile.is_active]
 | 
				
			||||||
 | 
					    return notification
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def do_send_typing_notification(notification):
 | 
				
			||||||
 | 
					    # type: (Dict[str, Any]) -> None
 | 
				
			||||||
 | 
					    notification = get_typing_notification_recipients(notification)
 | 
				
			||||||
 | 
					    event = dict(
 | 
				
			||||||
 | 
					            type            = 'typing',
 | 
				
			||||||
 | 
					            op              = notification['op'],
 | 
				
			||||||
 | 
					            sender          = notification['sender'],
 | 
				
			||||||
 | 
					            recipients      = notification['recipients'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Only deliver the message to active user recipients
 | 
				
			||||||
 | 
					    send_event(event, notification['active_recipients'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# check_send_typing_notification:
 | 
				
			||||||
 | 
					# Checks the typing notification and sends it
 | 
				
			||||||
 | 
					def check_send_typing_notification(sender, notification_to, operator):
 | 
				
			||||||
 | 
					    # type: (UserProfile, Sequence[text_type], text_type) -> None
 | 
				
			||||||
 | 
					    typing_notification = check_typing_notification(sender, notification_to, operator)
 | 
				
			||||||
 | 
					    do_send_typing_notification(typing_notification)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# check_typing_notification:
 | 
				
			||||||
 | 
					# Returns typing notification ready for sending with do_send_typing_notification on success
 | 
				
			||||||
 | 
					# or the error message (string) on error.
 | 
				
			||||||
 | 
					def check_typing_notification(sender, notification_to, operator):
 | 
				
			||||||
 | 
					    # type: (UserProfile, Sequence[text_type], text_type) -> Dict[str, Any]
 | 
				
			||||||
 | 
					    if len(notification_to) == 0:
 | 
				
			||||||
 | 
					        raise JsonableError(_('Missing parameter: \'to\' (recipient)'))
 | 
				
			||||||
 | 
					    elif operator not in ('start', 'stop'):
 | 
				
			||||||
 | 
					        raise JsonableError(_('Invalid \'op\' value (should be start or stop)'))
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            recipient = recipient_for_emails(notification_to, False,
 | 
				
			||||||
 | 
					                                             sender, sender)
 | 
				
			||||||
 | 
					        except ValidationError as e:
 | 
				
			||||||
 | 
					            assert isinstance(e.messages[0], six.string_types)
 | 
				
			||||||
 | 
					            raise JsonableError(e.messages[0])
 | 
				
			||||||
 | 
					    typing_notification = {'sender': sender, 'recipient': recipient, 'op': operator}
 | 
				
			||||||
 | 
					    return typing_notification
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def do_create_stream(realm, stream_name):
 | 
					def do_create_stream(realm, stream_name):
 | 
				
			||||||
    # type: (Realm, text_type) -> None
 | 
					    # type: (Realm, text_type) -> None
 | 
				
			||||||
    # This is used by a management command now, mostly to facilitate testing.  It
 | 
					    # This is used by a management command now, mostly to facilitate testing.  It
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										203
									
								
								zerver/tests/test_typing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								zerver/tests/test_typing.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,203 @@
 | 
				
			|||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					from __future__ import absolute_import
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ujson
 | 
				
			||||||
 | 
					from typing import Any, Dict, List
 | 
				
			||||||
 | 
					from six import string_types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from zerver.models import get_user_profile_by_email
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from zerver.lib.test_helpers import ZulipTestCase, tornado_redirected_to_list, get_display_recipient
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TypingNotificationOperatorTest(ZulipTestCase):
 | 
				
			||||||
 | 
					    def test_missing_parameter(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending typing notification without op parameter fails
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        sender = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        recipient = 'othello@zulip.com'
 | 
				
			||||||
 | 
					        result = self.client_post('/api/v1/typing', {'to': recipient},
 | 
				
			||||||
 | 
					                                  **self.api_auth(sender))
 | 
				
			||||||
 | 
					        self.assert_json_error(result, 'Missing \'op\' argument')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_invalid_parameter(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending typing notification with invalid value for op parameter fails
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        sender = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        recipient = 'othello@zulip.com'
 | 
				
			||||||
 | 
					        result = self.client_post('/api/v1/typing', {'to': recipient, 'op': 'foo'},
 | 
				
			||||||
 | 
					                                  **self.api_auth(sender))
 | 
				
			||||||
 | 
					        self.assert_json_error(result, 'Invalid \'op\' value (should be start or stop)')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TypingNotificationRecipientsTest(ZulipTestCase):
 | 
				
			||||||
 | 
					    def test_missing_recipient(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending typing notification without recipient fails
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        sender = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        result = self.client_post('/api/v1/typing', {'op': 'start'},
 | 
				
			||||||
 | 
					                                  **self.api_auth(sender))
 | 
				
			||||||
 | 
					        self.assert_json_error(result, 'Missing parameter: \'to\' (recipient)')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_invalid_recipient(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending typing notification to invalid recipient fails
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        sender = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        invalid = 'invalid email'
 | 
				
			||||||
 | 
					        result = self.client_post('/api/v1/typing', {'op': 'start', 'to': invalid},
 | 
				
			||||||
 | 
					                                  **self.api_auth(sender))
 | 
				
			||||||
 | 
					        self.assert_json_error(result, 'Invalid email \'' + invalid + '\'')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_single_recipient(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending typing notification to a single recipient is successful
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        sender = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        recipient = 'othello@zulip.com'
 | 
				
			||||||
 | 
					        expected_recipients = set([sender, recipient])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        events = [] # type: List[Dict[str, Any]]
 | 
				
			||||||
 | 
					        with tornado_redirected_to_list(events):
 | 
				
			||||||
 | 
					            result = self.client_post('/api/v1/typing', {'to': recipient,
 | 
				
			||||||
 | 
					                                                         'op': 'start'},
 | 
				
			||||||
 | 
					                                      **self.api_auth(sender))
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					        self.assertTrue(len(events) == 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        event = events[0]['event']
 | 
				
			||||||
 | 
					        event_recipient_emails = set(user['email'] for user in event['recipients'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertTrue(event['sender'] == get_user_profile_by_email(sender))
 | 
				
			||||||
 | 
					        self.assertTrue(event_recipient_emails == expected_recipients)
 | 
				
			||||||
 | 
					        self.assertTrue(event['type'] == 'typing')
 | 
				
			||||||
 | 
					        self.assertTrue(event['op'] == 'start')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_multiple_recipients(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending typing notification to a single recipient is successful
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        sender = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        recipient = ['othello@zulip.com', 'cordelia@zulip.com']
 | 
				
			||||||
 | 
					        expected_recipients = set(recipient) | set([sender])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        events = [] # type: List[Dict[str, Any]]
 | 
				
			||||||
 | 
					        with tornado_redirected_to_list(events):
 | 
				
			||||||
 | 
					            result = self.client_post('/api/v1/typing', {'to': ujson.dumps(recipient),
 | 
				
			||||||
 | 
					                                                         'op': 'start'},
 | 
				
			||||||
 | 
					                                      **self.api_auth(sender))
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					        self.assertTrue(len(events) == 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        event = events[0]['event']
 | 
				
			||||||
 | 
					        event_recipient_emails = set(user['email'] for user in event['recipients'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertTrue(event['sender'] == get_user_profile_by_email(sender))
 | 
				
			||||||
 | 
					        self.assertTrue(event_recipient_emails == expected_recipients)
 | 
				
			||||||
 | 
					        self.assertTrue(event['type'] == 'typing')
 | 
				
			||||||
 | 
					        self.assertTrue(event['op'] == 'start')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TypingStartedNotificationTest(ZulipTestCase):
 | 
				
			||||||
 | 
					    def test_send_notification_to_self_event(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending typing notification to yourself
 | 
				
			||||||
 | 
					        is successful.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        email = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        events = [] # type: List[Dict[str, Any]]
 | 
				
			||||||
 | 
					        with tornado_redirected_to_list(events):
 | 
				
			||||||
 | 
					            result = self.client_post('/api/v1/typing', {'to': email,
 | 
				
			||||||
 | 
					                                                         'op': 'start'},
 | 
				
			||||||
 | 
					                                      **self.api_auth(email))
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					        self.assertTrue(len(events) == 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        event = events[0]['event']
 | 
				
			||||||
 | 
					        event_recipient_emails = set(user['email'] for user in event['recipients'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertTrue(event['sender'] == get_user_profile_by_email(email))
 | 
				
			||||||
 | 
					        self.assertTrue(event_recipient_emails == set([email]))
 | 
				
			||||||
 | 
					        self.assertTrue(event['type'] == 'typing')
 | 
				
			||||||
 | 
					        self.assertTrue(event['op'] == 'start')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_send_notification_to_another_user_event(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending typing notification to another user
 | 
				
			||||||
 | 
					        is successful.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        sender = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        recipient = 'othello@zulip.com'
 | 
				
			||||||
 | 
					        expected_recipients = set([sender, recipient])
 | 
				
			||||||
 | 
					        events = [] # type: List[Dict[str, Any]]
 | 
				
			||||||
 | 
					        with tornado_redirected_to_list(events):
 | 
				
			||||||
 | 
					            result = self.client_post('/api/v1/typing', {'to': recipient,
 | 
				
			||||||
 | 
					                                                         'op': 'start'},
 | 
				
			||||||
 | 
					                                      **self.api_auth(sender))
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					        self.assertTrue(len(events) == 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        event = events[0]['event']
 | 
				
			||||||
 | 
					        event_recipient_emails = set(user['email'] for user in event['recipients'])
 | 
				
			||||||
 | 
					        self.assertTrue(event['sender'] == get_user_profile_by_email(sender))
 | 
				
			||||||
 | 
					        self.assertTrue(event_recipient_emails == expected_recipients)
 | 
				
			||||||
 | 
					        self.assertTrue(event['type'] == 'typing')
 | 
				
			||||||
 | 
					        self.assertTrue(event['op'] == 'start')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StoppedTypingNotificationTest(ZulipTestCase):
 | 
				
			||||||
 | 
					    def test_send_notification_to_self_event(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending stopped typing notification to yourself
 | 
				
			||||||
 | 
					        is successful.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        email = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        events = [] # type: List[Dict[str, Any]]
 | 
				
			||||||
 | 
					        with tornado_redirected_to_list(events):
 | 
				
			||||||
 | 
					            result = self.client_post('/api/v1/typing', {'to': email,
 | 
				
			||||||
 | 
					                                                         'op': 'stop'},
 | 
				
			||||||
 | 
					                                      **self.api_auth(email))
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					        self.assertTrue(len(events) == 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        event = events[0]['event']
 | 
				
			||||||
 | 
					        event_recipient_emails = set(user['email'] for user in event['recipients'])
 | 
				
			||||||
 | 
					        self.assertTrue(event['sender'] == get_user_profile_by_email(email))
 | 
				
			||||||
 | 
					        self.assertTrue(event_recipient_emails == set([email]))
 | 
				
			||||||
 | 
					        self.assertTrue(event['type'] == 'typing')
 | 
				
			||||||
 | 
					        self.assertTrue(event['op'] == 'stop')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_send_notification_to_another_user_event(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending stopped typing notification to another user
 | 
				
			||||||
 | 
					        is successful.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        sender = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        recipient = 'othello@zulip.com'
 | 
				
			||||||
 | 
					        expected_recipients = set([sender, recipient])
 | 
				
			||||||
 | 
					        events = [] # type: List[Dict[str, Any]]
 | 
				
			||||||
 | 
					        with tornado_redirected_to_list(events):
 | 
				
			||||||
 | 
					            result = self.client_post('/api/v1/typing', {'to': recipient,
 | 
				
			||||||
 | 
					                                                         'op': 'stop'},
 | 
				
			||||||
 | 
					                                      **self.api_auth(sender))
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					        self.assertTrue(len(events) == 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        event = events[0]['event']
 | 
				
			||||||
 | 
					        event_recipient_emails = set(user['email'] for user in event['recipients'])
 | 
				
			||||||
 | 
					        self.assertTrue(event['sender'] == get_user_profile_by_email(sender))
 | 
				
			||||||
 | 
					        self.assertTrue(event_recipient_emails == expected_recipients)
 | 
				
			||||||
 | 
					        self.assertTrue(event['type'] == 'typing')
 | 
				
			||||||
 | 
					        self.assertTrue(event['op'] == 'stop')
 | 
				
			||||||
							
								
								
									
										16
									
								
								zerver/views/typing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								zerver/views/typing.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					from django.http import HttpRequest, HttpResponse
 | 
				
			||||||
 | 
					from six import text_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from zerver.decorator import authenticated_json_post_view,\
 | 
				
			||||||
 | 
					    has_request_variables, REQ, JsonableError
 | 
				
			||||||
 | 
					from zerver.lib.actions import check_send_typing_notification, \
 | 
				
			||||||
 | 
					    extract_recipients
 | 
				
			||||||
 | 
					from zerver.lib.response import json_success
 | 
				
			||||||
 | 
					from zerver.models import UserProfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@has_request_variables
 | 
				
			||||||
 | 
					def send_notification_backend(request, user_profile, operator=REQ('op'),
 | 
				
			||||||
 | 
					                              notification_to = REQ('to', converter=extract_recipients, default=[])):
 | 
				
			||||||
 | 
					    # type: (HttpRequest, UserProfile, text_type, List[text_type]) -> HttpResponse
 | 
				
			||||||
 | 
					    check_send_typing_notification(user_profile, notification_to, operator)
 | 
				
			||||||
 | 
					    return json_success()
 | 
				
			||||||
@@ -169,6 +169,11 @@ v1_api_and_json_patterns = [
 | 
				
			|||||||
    url(r'^messages/flags$', 'zerver.lib.rest.rest_dispatch',
 | 
					    url(r'^messages/flags$', 'zerver.lib.rest.rest_dispatch',
 | 
				
			||||||
        {'POST': 'zerver.views.messages.update_message_flags'}),
 | 
					        {'POST': 'zerver.views.messages.update_message_flags'}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # typing -> zerver.views.typing
 | 
				
			||||||
 | 
					    # POST sends a typing notification event to recipients
 | 
				
			||||||
 | 
					    url(r'^typing$', 'zerver.lib.rest.rest_dispatch',
 | 
				
			||||||
 | 
					        {'POST': 'zerver.views.typing.send_notification_backend'}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # user_uploads -> zerver.views.upload
 | 
					    # user_uploads -> zerver.views.upload
 | 
				
			||||||
    url(r'^user_uploads$', 'zerver.lib.rest.rest_dispatch',
 | 
					    url(r'^user_uploads$', 'zerver.lib.rest.rest_dispatch',
 | 
				
			||||||
        {'POST': 'zerver.views.upload.upload_file_backend'}),
 | 
					        {'POST': 'zerver.views.upload.upload_file_backend'}),
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user