Remove rest_dispatch hack and optimize imports.

For a long time, rest_dispatch has had this hack where we have to
create a copy of it in each views file using it, in order to directly
access the globals list in that file.  This removes that hack, instead
making rest_dispatch just use Django's import_string to access the
target method to use.

[tweaked and reorganized from acrefoot's original branch in various
ways by tabbott]
This commit is contained in:
acrefoot 2016-06-23 17:26:09 -07:00 committed by Tim Abbott
parent aebd84cb1b
commit e4ed9195dc
16 changed files with 106 additions and 159 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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.

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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),

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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<emoji_name>[0-9a-zA-Z.\-_]+(?<![.\-_]))$', 'rest_dispatch',
{'DELETE': 'delete_emoji'}),
) + patterns('zerver.views.users',
# users -> zerver.views.users
url(r'^users$', 'rest_dispatch',
{'GET': 'get_members_backend',
'POST': 'create_user_backend'}),
url(r'^users/(?P<email>(?!me)[^/]*)/reactivate$', 'rest_dispatch',
{'POST': 'reactivate_user_backend'}),
url(r'^users/(?P<email>(?!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<email>(?!me)[^/]*)/api_key/regenerate$', 'rest_dispatch',
{'POST': 'regenerate_bot_api_key'}),
url(r'^bots/(?P<email>(?!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<emoji_name>[0-9a-zA-Z.\-_]+(?<![.\-_]))$', 'zerver.lib.rest.rest_dispatch',
{'DELETE': 'zerver.views.realm_emoji.delete_emoji'}),
# users -> 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<email>(?!me)[^/]*)/reactivate$', 'zerver.lib.rest.rest_dispatch',
{'POST': 'zerver.views.users.reactivate_user_backend'}),
url(r'^users/(?P<email>(?!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<email>(?!me)[^/]*)/api_key/regenerate$', 'zerver.lib.rest.rest_dispatch',
{'POST': 'zerver.views.users.regenerate_bot_api_key'}),
url(r'^bots/(?P<email>(?!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<stream_name>.*)/members$', 'rest_dispatch',
{'GET': 'get_subscribers_backend'}),
url(r'^streams/(?P<stream_name>.*)$', '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<stream_name>.*)/members$', 'zerver.lib.rest.rest_dispatch',
{'GET': 'zerver.views.streams.get_subscribers_backend'}),
url(r'^streams/(?P<stream_name>.*)$', '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 += [