diff --git a/zerver/lib/rest.py b/zerver/lib/rest.py index dda5aea53d..7dbcf71707 100644 --- a/zerver/lib/rest.py +++ b/zerver/lib/rest.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from typing import Any, Dict +from django.utils.module_loading import import_string from django.utils.translation import ugettext as _ from django.views.decorators.csrf import csrf_exempt, csrf_protect @@ -14,8 +15,8 @@ from django.conf import settings METHODS = ('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH') @csrf_exempt -def rest_dispatch(request, globals_list, **kwargs): - # type: (HttpRequest, Dict[str, Any], **Any) -> HttpResponse +def rest_dispatch(request, **kwargs): + # type: (HttpRequest, **Any) -> HttpResponse """Dispatch to a REST API endpoint. Unauthenticated endpoints should not use this, as authentication is verified @@ -35,9 +36,8 @@ def rest_dispatch(request, globals_list, **kwargs): Any keyword args that are *not* HTTP methods are passed through to the target function. - Note that we search views.py globals for the function to call, so never - make a urls.py pattern put user input into a variable called GET, POST, - etc. + Never make a urls.py pattern put user input into a variable called GET, POST, + etc, as that is where we route HTTP verbs to target functions. """ supported_methods = {} # type: Dict[str, Any] # duplicate kwargs so we can mutate the original as we go @@ -60,7 +60,7 @@ def rest_dispatch(request, globals_list, **kwargs): method_to_use = request.META["zulip.emulated_method"] if method_to_use in supported_methods: - target_function = globals_list[supported_methods[method_to_use]] + target_function = import_string(supported_methods[method_to_use]) # Set request._query for update_activity_user(), which is called # by some of the later wrappers. diff --git a/zerver/tests/test_urls.py b/zerver/tests/test_urls.py index aa3df73fcd..eacce9b55c 100644 --- a/zerver/tests/test_urls.py +++ b/zerver/tests/test_urls.py @@ -20,8 +20,8 @@ class URLResolutionTest(TestCase): if not (hasattr(pattern, "_callback_str") and hasattr(pattern, "default_args")): continue - for view in pattern.default_args.values(): - module_name = pattern._callback_str.replace(".rest_dispatch", "") + for func_string in pattern.default_args.values(): + module_name, view = func_string.rsplit('.', 1) self.check_function_exists(module_name, view) # Tests function-based views declared in urls.urlpatterns for diff --git a/zerver/tornadoviews.py b/zerver/tornadoviews.py index cc7b7417ac..cc307d6510 100644 --- a/zerver/tornadoviews.py +++ b/zerver/tornadoviews.py @@ -1,7 +1,6 @@ from __future__ import absolute_import from django.utils.translation import ugettext as _ -from django.views.decorators.csrf import csrf_exempt from django.http import HttpRequest, HttpResponse from six import text_type @@ -21,9 +20,6 @@ from typing import Union, Optional, Iterable, Sequence, List, Any import time import ujson -from zerver.lib.rest import rest_dispatch as _rest_dispatch -rest_dispatch = csrf_exempt((lambda request, *args, **kwargs: _rest_dispatch(request, globals(), *args, **kwargs))) - @internal_notify_view def notify(request): # type: (HttpRequest) -> HttpResponse diff --git a/zerver/views/__init__.py b/zerver/views/__init__.py index 6be761f61c..a52c80df32 100644 --- a/zerver/views/__init__.py +++ b/zerver/views/__init__.py @@ -70,8 +70,6 @@ import hashlib import hmac from zproject.jinja2 import render_to_response -from zerver.lib.rest import rest_dispatch as _rest_dispatch -rest_dispatch = csrf_exempt((lambda request, *args, **kwargs: _rest_dispatch(request, globals(), *args, **kwargs))) def name_changes_disabled(realm): # type: (Optional[Realm]) -> bool diff --git a/zerver/views/alert_words.py b/zerver/views/alert_words.py index 66fe9fe462..b1e0f4c7ff 100644 --- a/zerver/views/alert_words.py +++ b/zerver/views/alert_words.py @@ -1,21 +1,17 @@ from __future__ import absolute_import -from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse, HttpRequest from typing import List from zerver.models import UserProfile -from zerver.decorator import authenticated_json_post_view, has_request_variables, REQ +from zerver.decorator import has_request_variables, REQ from zerver.lib.response import json_success from zerver.lib.validator import check_list, check_string from zerver.lib.actions import do_add_alert_words, do_remove_alert_words, do_set_alert_words from zerver.lib.alert_words import user_alert_words -from zerver.lib.rest import rest_dispatch as _rest_dispatch -rest_dispatch = csrf_exempt((lambda request, *args, **kwargs: _rest_dispatch(request, globals(), *args, **kwargs))) - from six import text_type def list_alert_words(request, user_profile): diff --git a/zerver/views/messages.py b/zerver/views/messages.py index c73e6d9b8f..29bd5575a6 100644 --- a/zerver/views/messages.py +++ b/zerver/views/messages.py @@ -14,7 +14,6 @@ from zerver.decorator import authenticated_api_view, authenticated_json_post_vie has_request_variables, REQ, JsonableError, \ to_non_negative_int, to_non_negative_float from django.utils.html import escape as escape_html -from django.views.decorators.csrf import csrf_exempt from zerver.lib import bugdown from zerver.lib.actions import recipient_for_emails, do_update_message_flags, \ compute_mit_user_fullname, compute_irc_user_fullname, compute_jabber_user_fullname, \ @@ -42,10 +41,9 @@ from sqlalchemy.sql import select, join, column, literal_column, literal, and_, import re import ujson -from zerver.lib.rest import rest_dispatch as _rest_dispatch + from six.moves import map import six -rest_dispatch = csrf_exempt((lambda request, *args, **kwargs: _rest_dispatch(request, globals(), *args, **kwargs))) # This is a Pool that doesn't close connections. Therefore it can be used with # existing Django database connections. diff --git a/zerver/views/realm_emoji.py b/zerver/views/realm_emoji.py index c510dbd964..6e442d35bc 100644 --- a/zerver/views/realm_emoji.py +++ b/zerver/views/realm_emoji.py @@ -1,16 +1,12 @@ from django.http import HttpRequest, HttpResponse from django.core.exceptions import ValidationError -from django.views.decorators.csrf import csrf_exempt from zerver.models import UserProfile from zerver.lib.response import json_success, json_error from zerver.lib.actions import check_add_realm_emoji, do_remove_realm_emoji -from zerver.lib.rest import rest_dispatch as _rest_dispatch from six import text_type -rest_dispatch = csrf_exempt((lambda request, *args, **kwargs: _rest_dispatch(request, globals(), *args, **kwargs))) - def list_emoji(request, user_profile): # type: (HttpRequest, UserProfile) -> HttpResponse diff --git a/zerver/views/report.py b/zerver/views/report.py index c261a99a08..bc86551767 100644 --- a/zerver/views/report.py +++ b/zerver/views/report.py @@ -3,12 +3,10 @@ from typing import Any from django.conf import settings from django.http import HttpRequest, HttpResponse -from django.views.decorators.csrf import csrf_exempt from zerver.decorator import authenticated_json_post_view, has_request_variables, REQ, \ to_non_negative_int from zerver.lib.response import json_success -from zerver.lib.rest import rest_dispatch as _rest_dispatch from zerver.lib.queue import queue_json_publish from zerver.lib.unminify import SourceMap from zerver.lib.utils import statsd, statsd_key @@ -20,8 +18,6 @@ from six import text_type import subprocess import os -rest_dispatch = csrf_exempt((lambda request, *args, **kwargs: _rest_dispatch(request, globals(), *args, **kwargs))) - # Read the source map information for decoding JavaScript backtraces js_source_map = None if not (settings.DEBUG or settings.TEST_SUITE): diff --git a/zerver/views/streams.py b/zerver/views/streams.py index d9104ae033..37dc7c8a0a 100644 --- a/zerver/views/streams.py +++ b/zerver/views/streams.py @@ -5,7 +5,6 @@ from django.utils.translation import ugettext as _ from django.conf import settings from django.db import transaction from django.http import HttpRequest, HttpResponse -from django.views.decorators.csrf import csrf_exempt from zerver.lib.request import JsonableError, REQ, has_request_variables from zerver.decorator import authenticated_json_post_view, \ @@ -29,12 +28,9 @@ from collections import defaultdict import ujson from six.moves import urllib -from zerver.lib.rest import rest_dispatch as _rest_dispatch import six from six import text_type -rest_dispatch = csrf_exempt((lambda request, *args, **kwargs: _rest_dispatch(request, globals(), *args, **kwargs))) - def list_to_streams(streams_raw, user_profile, autocreate=False, invite_only=False): # type: (Iterable[text_type], UserProfile, Optional[bool], Optional[bool]) -> Tuple[List[Stream], List[Stream]] """Converts plaintext stream names to a list of Streams, validating input in the process diff --git a/zerver/views/tutorial.py b/zerver/views/tutorial.py index c22c88a8cb..998a193f7b 100644 --- a/zerver/views/tutorial.py +++ b/zerver/views/tutorial.py @@ -1,7 +1,6 @@ from __future__ import absolute_import from django.utils.translation import ugettext as _ -from django.views.decorators.csrf import csrf_exempt from django.http import HttpRequest, HttpResponse from zerver.decorator import authenticated_json_post_view, has_request_variables, REQ @@ -10,9 +9,6 @@ from zerver.lib.response import json_error, json_success from zerver.lib.validator import check_string from zerver.models import UserProfile -from zerver.lib.rest import rest_dispatch as _rest_dispatch -rest_dispatch = csrf_exempt((lambda request, *args, **kwargs: _rest_dispatch(request, globals(), *args, **kwargs))) - @authenticated_json_post_view @has_request_variables def json_tutorial_send_message(request, user_profile, type=REQ(validator=check_string), diff --git a/zerver/views/user_settings.py b/zerver/views/user_settings.py index c314890101..95a01d3a7b 100644 --- a/zerver/views/user_settings.py +++ b/zerver/views/user_settings.py @@ -4,7 +4,6 @@ from six import text_type from django.utils.translation import ugettext as _ from django.conf import settings -from django.views.decorators.csrf import csrf_exempt from django.contrib.auth import authenticate from django.http import HttpRequest, HttpResponse @@ -23,9 +22,6 @@ from zerver.lib.upload import upload_avatar_image from zerver.lib.validator import check_bool from zerver.models import UserProfile, Realm -from zerver.lib.rest import rest_dispatch as _rest_dispatch -rest_dispatch = csrf_exempt((lambda request, *args, **kwargs: _rest_dispatch(request, globals(), *args, **kwargs))) - def name_changes_disabled(realm): # type: (Realm) -> bool return settings.NAME_CHANGES_DISABLED or realm.name_changes_disabled diff --git a/zerver/views/users.py b/zerver/views/users.py index 7f0226893f..98cdb8fb91 100644 --- a/zerver/views/users.py +++ b/zerver/views/users.py @@ -4,7 +4,6 @@ from django.http import HttpRequest, HttpResponse from django.utils.translation import ugettext as _ from django.shortcuts import redirect -from django.views.decorators.csrf import csrf_exempt from six.moves import map from zerver.decorator import has_request_variables, REQ, JsonableError, \ @@ -21,9 +20,6 @@ from zerver.lib.validator import check_bool from zerver.models import UserProfile, Stream, Realm, get_user_profile_by_email, \ get_stream, email_allowed_for_realm -from zerver.lib.rest import rest_dispatch as _rest_dispatch -rest_dispatch = csrf_exempt((lambda request, *args, **kwargs: _rest_dispatch(request, globals(), *args, **kwargs))) - from six import text_type from typing import Optional, Dict, Any diff --git a/zilencer/urls.py b/zilencer/urls.py index b3bcb73964..b9febfd134 100644 --- a/zilencer/urls.py +++ b/zilencer/urls.py @@ -11,16 +11,15 @@ i18n_urlpatterns = [ ] # Zilencer views following the REST API style -v1_api_and_json_patterns = patterns('zilencer.views', - url('^feedback$', 'rest_dispatch', - {'POST': 'submit_feedback'}), - url('^report_error$', 'rest_dispatch', - {'POST': 'report_error'}), - url('^endpoints$', 'lookup_endpoints_for_user'), -) +v1_api_and_json_patterns = [ + url('^feedback$', 'zerver.lib.rest.rest_dispatch', + {'POST': 'zilencer.views.submit_feedback'}), + url('^report_error$', 'zerver.lib.rest.rest_dispatch', + {'POST': 'zilencer.views.report_error'}), + url('^endpoints$', 'zilencer.views.lookup_endpoints_for_user'), +] -urlpatterns = patterns('', +urlpatterns = [ url(r'^api/v1/', include(v1_api_and_json_patterns)), url(r'^json/', include(v1_api_and_json_patterns)), - *i18n_urlpatterns -) +] + i18n_urlpatterns diff --git a/zilencer/views.py b/zilencer/views.py index 3c818f22b5..68479de345 100644 --- a/zilencer/views.py +++ b/zilencer/views.py @@ -11,19 +11,16 @@ from zilencer.models import Deployment from zerver.decorator import has_request_variables, REQ from zerver.lib.actions import internal_send_message from zerver.lib.redis_utils import get_redis_client -from zerver.lib.response import json_success, json_error, json_response, json_method_not_allowed -from zerver.lib.rest import rest_dispatch as _rest_dispatch +from zerver.lib.response import json_success, json_error, json_response from zerver.lib.validator import check_dict from zerver.models import get_realm, get_user_profile_by_email, resolve_email_to_domain, \ UserProfile, Realm from .error_notify import notify_server_error, notify_browser_error -from django.conf import settings + import time from typing import Dict, Optional, Any -rest_dispatch = csrf_exempt((lambda request, *args, **kwargs: _rest_dispatch(request, globals(), *args, **kwargs))) - client = get_redis_client() def has_enough_time_expired_since_last_message(sender_email, min_delay): diff --git a/zproject/legacy_urls.py b/zproject/legacy_urls.py index 6684eafe65..0fb07b6b86 100644 --- a/zproject/legacy_urls.py +++ b/zproject/legacy_urls.py @@ -1,10 +1,4 @@ -from django.conf import settings -from django.conf.urls import patterns, url, include -from django.conf.urls.i18n import i18n_patterns -from django.views.generic import TemplateView, RedirectView -from django.utils.module_loading import import_string -import os.path -import zerver.forms +from django.conf.urls import url # Future endpoints should add to urls.py, which includes these legacy urls diff --git a/zproject/urls.py b/zproject/urls.py index b11dbfac82..d54680a6d6 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -116,118 +116,111 @@ urls = list(i18n_urls) # See rest_dispatch in zerver.lib.rest for an explanation of auth methods used # # All of these paths are accessed by either a /json or /api prefix -v1_api_and_json_patterns = patterns('zerver.views', +v1_api_and_json_patterns = [ # realm-level calls - url(r'^export$', 'rest_dispatch', - {'GET': 'export'}), - url(r'^realm$', 'rest_dispatch', - {'PATCH': 'update_realm'}), + url(r'^export$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.views.export'}), + url(r'^realm$', 'zerver.lib.rest.rest_dispatch', + {'PATCH': 'zerver.views.update_realm'}), # Returns a 204, used by desktop app to verify connectivity status - url(r'generate_204$', 'generate_204'), -) + patterns('zerver.views.realm_emoji', - # realm/emoji -> zerver.views.realm_emoji - url(r'^realm/emoji$', 'rest_dispatch', - {'GET': 'list_emoji', - 'PUT': 'upload_emoji'}), - url(r'^realm/emoji/(?P[0-9a-zA-Z.\-_]+(? zerver.views.users - url(r'^users$', 'rest_dispatch', - {'GET': 'get_members_backend', - 'POST': 'create_user_backend'}), - url(r'^users/(?P(?!me)[^/]*)/reactivate$', 'rest_dispatch', - {'POST': 'reactivate_user_backend'}), - url(r'^users/(?P(?!me)[^/]*)$', 'rest_dispatch', - {'PATCH': 'update_user_backend', - 'DELETE': 'deactivate_user_backend'}), - url(r'^bots$', 'rest_dispatch', - {'GET': 'get_bots_backend', - 'POST': 'add_bot_backend'}), - url(r'^bots/(?P(?!me)[^/]*)/api_key/regenerate$', 'rest_dispatch', - {'POST': 'regenerate_bot_api_key'}), - url(r'^bots/(?P(?!me)[^/]*)$', 'rest_dispatch', - {'PATCH': 'patch_bot_backend', - 'DELETE': 'deactivate_bot_backend'}), + url(r'generate_204$', 'zerver.views.generate_204'), + + # realm/emoji -> zerver.views.realm_emoji + url(r'^realm/emoji$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.views.realm_emoji.list_emoji', + 'PUT': 'zerver.views.realm_emoji.upload_emoji'}), + url(r'^realm/emoji/(?P[0-9a-zA-Z.\-_]+(? zerver.views.users + url(r'^users$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.views.users.get_members_backend', + 'POST': 'zerver.views.users.create_user_backend'}), + url(r'^users/(?P(?!me)[^/]*)/reactivate$', 'zerver.lib.rest.rest_dispatch', + {'POST': 'zerver.views.users.reactivate_user_backend'}), + url(r'^users/(?P(?!me)[^/]*)$', 'zerver.lib.rest.rest_dispatch', + {'PATCH': 'zerver.views.users.update_user_backend', + 'DELETE': 'zerver.views.users.deactivate_user_backend'}), + url(r'^bots$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.views.users.get_bots_backend', + 'POST': 'zerver.views.users.add_bot_backend'}), + url(r'^bots/(?P(?!me)[^/]*)/api_key/regenerate$', 'zerver.lib.rest.rest_dispatch', + {'POST': 'zerver.views.users.regenerate_bot_api_key'}), + url(r'^bots/(?P(?!me)[^/]*)$', 'zerver.lib.rest.rest_dispatch', + {'PATCH': 'zerver.views.users.patch_bot_backend', + 'DELETE': 'zerver.views.users.deactivate_bot_backend'}), -) + patterns('zerver.views.messages', # messages -> zerver.views.messages # GET returns messages, possibly filtered, POST sends a message - url(r'^messages$', 'rest_dispatch', - {'GET': 'get_old_messages_backend', - 'PATCH': 'update_message_backend', - 'POST': 'send_message_backend'}), - url(r'^messages/render$', 'rest_dispatch', - {'GET': 'render_message_backend'}), - url(r'^messages/flags$', 'rest_dispatch', - {'POST': 'update_message_flags'}), + url(r'^messages$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.views.messages.get_old_messages_backend', + 'PATCH': 'zerver.views.messages.update_message_backend', + 'POST': 'zerver.views.messages.send_message_backend'}), + url(r'^messages/render$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.views.messages.render_message_backend'}), + url(r'^messages/flags$', 'zerver.lib.rest.rest_dispatch', + {'POST': 'zerver.views.messages.update_message_flags'}), -) + patterns('zerver.views', # users/me -> zerver.views - url(r'^users/me$', 'rest_dispatch', - {'GET': 'get_profile_backend'}), - url(r'^users/me/pointer$', 'rest_dispatch', - {'GET': 'get_pointer_backend', - 'PUT': 'update_pointer_backend'}), - url(r'^users/me/presence$', 'rest_dispatch', - {'POST': 'update_active_status_backend'}), + url(r'^users/me$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.views.get_profile_backend'}), + url(r'^users/me/pointer$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.views.get_pointer_backend', + 'PUT': 'zerver.views.update_pointer_backend'}), + url(r'^users/me/presence$', 'zerver.lib.rest.rest_dispatch', + {'POST': 'zerver.views.update_active_status_backend'}), # Endpoint used by mobile devices to register their push # notification credentials - url(r'^users/me/apns_device_token$', 'rest_dispatch', - {'POST' : 'add_apns_device_token', - 'DELETE': 'remove_apns_device_token'}), - url(r'^users/me/android_gcm_reg_id$', 'rest_dispatch', - {'POST': 'add_android_reg_id', - 'DELETE': 'remove_android_reg_id'}), + url(r'^users/me/apns_device_token$', 'zerver.lib.rest.rest_dispatch', + {'POST': 'zerver.views.add_apns_device_token', + 'DELETE': 'zerver.views.remove_apns_device_token'}), + url(r'^users/me/android_gcm_reg_id$', 'zerver.lib.rest.rest_dispatch', + {'POST': 'zerver.views.add_android_reg_id', + 'DELETE': 'zerver.views.remove_android_reg_id'}), -) + patterns('zerver.views.user_settings', # users/me -> zerver.views.user_settings - url(r'^users/me/api_key/regenerate$', 'rest_dispatch', - {'POST': 'regenerate_api_key'}), - url(r'^users/me/enter-sends$', 'rest_dispatch', - {'POST': 'change_enter_sends'}), + url(r'^users/me/api_key/regenerate$', 'zerver.lib.rest.rest_dispatch', + {'POST': 'zerver.views.user_settings.regenerate_api_key'}), + url(r'^users/me/enter-sends$', 'zerver.lib.rest.rest_dispatch', + {'POST': 'zerver.views.user_settings.change_enter_sends'}), -) + patterns('zerver.views.alert_words', # users/me/alert_words -> zerver.views.alert_words - url(r'^users/me/alert_words$', 'rest_dispatch', - {'GET': 'list_alert_words', - 'POST': 'set_alert_words', - 'PUT': 'add_alert_words', - 'DELETE': 'remove_alert_words'}), + url(r'^users/me/alert_words$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.views.alert_words.list_alert_words', + 'POST': 'zerver.views.alert_words.set_alert_words', + 'PUT': 'zerver.views.alert_words.add_alert_words', + 'DELETE': 'zerver.views.alert_words.remove_alert_words'}), -) + patterns('zerver.views.streams', # streams -> zerver.views.streams - url(r'^streams$', 'rest_dispatch', - {'GET': 'get_streams_backend'}), + url(r'^streams$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.views.streams.get_streams_backend'}), # GET returns "stream info" (undefined currently?), HEAD returns whether stream exists (200 or 404) - url(r'^streams/(?P.*)/members$', 'rest_dispatch', - {'GET': 'get_subscribers_backend'}), - url(r'^streams/(?P.*)$', 'rest_dispatch', - {'HEAD': 'stream_exists_backend', - 'GET': 'stream_exists_backend', - 'PATCH': 'update_stream_backend', - 'DELETE': 'deactivate_stream_backend'}), - url(r'^default_streams$', 'rest_dispatch', - {'PUT': 'add_default_stream', - 'DELETE': 'remove_default_stream'}), + url(r'^streams/(?P.*)/members$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.views.streams.get_subscribers_backend'}), + url(r'^streams/(?P.*)$', 'zerver.lib.rest.rest_dispatch', + {'HEAD': 'zerver.views.streams.stream_exists_backend', + 'GET': 'zerver.views.streams.stream_exists_backend', + 'PATCH': 'zerver.views.streams.update_stream_backend', + 'DELETE': 'zerver.views.streams.deactivate_stream_backend'}), + url(r'^default_streams$', 'zerver.lib.rest.rest_dispatch', + {'PUT': 'zerver.views.streams.add_default_stream', + 'DELETE': 'zerver.views.streams.remove_default_stream'}), # GET lists your streams, POST bulk adds, PATCH bulk modifies/removes - url(r'^users/me/subscriptions$', 'rest_dispatch', - {'GET': 'list_subscriptions_backend', - 'POST': 'add_subscriptions_backend', - 'PATCH': 'update_subscriptions_backend'}), + url(r'^users/me/subscriptions$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.views.streams.list_subscriptions_backend', + 'POST': 'zerver.views.streams.add_subscriptions_backend', + 'PATCH': 'zerver.views.streams.update_subscriptions_backend'}), -) + patterns('zerver.views', - # Used to register for an event queue in tornado - url(r'^register$', 'rest_dispatch', - {'POST': 'api_events_register'}), + # used to register for an event queue in tornado + url(r'^register$', 'zerver.lib.rest.rest_dispatch', + {'POST': 'zerver.views.api_events_register'}), -) + patterns('zerver.tornadoviews', # events -> zerver.tornadoviews - url(r'^events$', 'rest_dispatch', - {'GET': 'get_events_backend', - 'DELETE': 'cleanup_event_queue'}), -) + url(r'^events$', 'zerver.lib.rest.rest_dispatch', + {'GET': 'zerver.tornadoviews.get_events_backend', + 'DELETE': 'zerver.tornadoviews.cleanup_event_queue'}), +] # Include the dual-use patterns twice urls += [