Skip to content

Commit c1c529f

Browse files
committed
Merged [5614] and [5622] from mcr@sandelman.ca: added dajaxice.
- Legacy-Id: 5786 Note: SVN reference [5614] has been migrated to Git commit 7e0e027 Note: SVN reference [5622] has been migrated to Git commit bde631c
1 parent ec2550d commit c1c529f

29 files changed

Lines changed: 1289 additions & 0 deletions

dajaxice/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VERSION = (0, 2, 0, 0, 'beta')

dajaxice/core/Dajaxice.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import logging
2+
3+
from django.conf import settings
4+
5+
# Python 2.7 has an importlib with import_module.
6+
# For older Pythons, Django's bundled copy provides it.
7+
# For older Django's dajaxice reduced_import_module.
8+
try:
9+
from importlib import import_module
10+
except:
11+
try:
12+
from django.utils.importlib import import_module
13+
except:
14+
from dajaxice.utils import simple_import_module as import_module
15+
16+
log = logging.getLogger('dajaxice.DajaxiceRequest')
17+
18+
19+
class DajaxiceFunction(object):
20+
21+
def __init__(self, name, path, doc=None):
22+
self.name = name
23+
self.path = path
24+
self.doc = doc
25+
26+
def get_callable_path(self):
27+
return '%s.%s' % (self.path.replace('.ajax', ''), self.name)
28+
29+
def __cmp__(self, other):
30+
return (self.name == other.name and self.path == other.path)
31+
32+
33+
class DajaxiceModule(object):
34+
def __init__(self, module):
35+
self.functions = []
36+
self.sub_modules = []
37+
self.name = module[0]
38+
39+
sub_module = module[1:]
40+
if len(sub_module) != 0:
41+
self.add_submodule(sub_module)
42+
43+
def get_module(self, module):
44+
"""
45+
Recursively get_module util we found it.
46+
"""
47+
if len(module) == 0:
48+
return self
49+
50+
for dajaxice_module in self.sub_modules:
51+
if dajaxice_module.name == module[0]:
52+
return dajaxice_module.get_module(module[1:])
53+
return None
54+
55+
def add_function(self, function):
56+
self.functions.append(function)
57+
58+
def has_sub_modules(self):
59+
return len(self.sub_modules) > 0
60+
61+
def add_submodule(self, module):
62+
"""
63+
Recursively add_submodule, if it's not registered, create it.
64+
"""
65+
if len(module) == 0:
66+
return
67+
else:
68+
sub_module = self.exist_submodule(module[0])
69+
70+
if type(sub_module) == int:
71+
self.sub_modules[sub_module].add_submodule(module[1:])
72+
else:
73+
self.sub_modules.append(DajaxiceModule(module))
74+
75+
def exist_submodule(self, name):
76+
"""
77+
Check if submodule name was already registered.
78+
"""
79+
for module in self.sub_modules:
80+
if module.name == name:
81+
return self.sub_modules.index(module)
82+
return False
83+
84+
85+
class Dajaxice(object):
86+
def __init__(self):
87+
self._registry = []
88+
self._callable = []
89+
90+
for function in getattr(settings, 'DAJAXICE_FUNCTIONS', ()):
91+
function = function.rsplit('.', 1)
92+
self.register_function(function[0], function[1])
93+
94+
def register(self, function):
95+
self.register_function(function.__module__, function.__name__, function.__doc__)
96+
97+
def register_function(self, module, name, doc=None):
98+
"""
99+
Register function at 'module' depth
100+
"""
101+
#Create the dajaxice function.
102+
function = DajaxiceFunction(name=name, path=module, doc=doc)
103+
104+
#Check for already registered functions.
105+
full_path = '%s.%s' % (module, name)
106+
if full_path in self._callable:
107+
log.warning('%s already registered as dajaxice function.' % full_path)
108+
return
109+
110+
self._callable.append(full_path)
111+
112+
#Dajaxice path without ajax.
113+
module_without_ajax = module.replace('.ajax', '').split('.')
114+
115+
#Register module if necessary.
116+
exist_module = self._exist_module(module_without_ajax[0])
117+
118+
if type(exist_module) == int:
119+
self._registry[exist_module].add_submodule(module_without_ajax[1:])
120+
else:
121+
self._registry.append(DajaxiceModule(module_without_ajax))
122+
123+
#Register Function
124+
module = self.get_module(module_without_ajax)
125+
if module:
126+
module.add_function(function)
127+
128+
def get_module(self, module):
129+
"""
130+
Recursively get module from registry
131+
"""
132+
for dajaxice_module in self._registry:
133+
if dajaxice_module.name == module[0]:
134+
return dajaxice_module.get_module(module[1:])
135+
return None
136+
137+
def is_callable(self, name):
138+
return name in self._callable
139+
140+
def _exist_module(self, module_name):
141+
for module in self._registry:
142+
if module.name == module_name:
143+
return self._registry.index(module)
144+
return False
145+
146+
def get_functions(self):
147+
return self._registry
148+
149+
150+
LOADING_DAJAXICE = False
151+
152+
153+
def dajaxice_autodiscover():
154+
"""
155+
Auto-discover INSTALLED_APPS ajax.py modules and fail silently when
156+
not present.
157+
NOTE: dajaxice_autodiscover was inspired/copied from django.contrib.admin autodiscover
158+
"""
159+
global LOADING_DAJAXICE
160+
if LOADING_DAJAXICE:
161+
return
162+
LOADING_DAJAXICE = True
163+
164+
import imp
165+
from django.conf import settings
166+
167+
for app in settings.INSTALLED_APPS:
168+
169+
try:
170+
app_path = import_module(app).__path__
171+
except AttributeError:
172+
continue
173+
174+
try:
175+
imp.find_module('ajax', app_path)
176+
except ImportError:
177+
continue
178+
179+
import_module("%s.ajax" % app)
180+
181+
LOADING_DAJAXICE = False

dajaxice/core/DajaxiceRequest.py

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
#----------------------------------------------------------------------
2+
# Copyright (c) 2009-2011 Benito Jorge Bastida
3+
# All rights reserved.
4+
# Redistribution and use in source and binary forms, with or without
5+
# modification, are permitted provided that the following conditions are
6+
# met:
7+
#
8+
# o Redistributions of source code must retain the above copyright
9+
# notice, this list of conditions, and the disclaimer that follows.
10+
#
11+
# o Redistributions in binary form must reproduce the above copyright
12+
# notice, this list of conditions, and the following disclaimer in
13+
# the documentation and/or other materials provided with the
14+
# distribution.
15+
#
16+
# o Neither the name of Digital Creations nor the names of its
17+
# contributors may be used to endorse or promote products derived
18+
# from this software without specific prior written permission.
19+
#
20+
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS
21+
# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23+
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL
24+
# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27+
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
28+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
29+
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
30+
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31+
# DAMAGE.
32+
#----------------------------------------------------------------------
33+
34+
import os
35+
import sys
36+
import logging
37+
import traceback
38+
39+
from django.conf import settings
40+
from django.utils import simplejson
41+
from django.http import HttpResponse
42+
43+
from dajaxice.core import dajaxice_functions
44+
from dajaxice.exceptions import FunctionNotCallableError, DajaxiceImportError
45+
46+
log = logging.getLogger('dajaxice.DajaxiceRequest')
47+
48+
# Python 2.7 has an importlib with import_module.
49+
# For older Pythons, Django's bundled copy provides it.
50+
# For older Django's dajaxice reduced_import_module.
51+
try:
52+
from importlib import import_module
53+
except:
54+
try:
55+
from django.utils.importlib import import_module
56+
except:
57+
from dajaxice.utils import simple_import_module as import_module
58+
59+
60+
def safe_dict(d):
61+
"""
62+
Recursively clone json structure with UTF-8 dictionary keys
63+
http://www.gossamer-threads.com/lists/python/bugs/684379
64+
"""
65+
if isinstance(d, dict):
66+
return dict([(k.encode('utf-8'), safe_dict(v)) for k, v in d.iteritems()])
67+
elif isinstance(d, list):
68+
return [safe_dict(x) for x in d]
69+
else:
70+
return d
71+
72+
73+
class DajaxiceRequest(object):
74+
75+
def __init__(self, request, call):
76+
call = call.rsplit('.', 1)
77+
self.app_name = call[0]
78+
self.method = call[1]
79+
self.request = request
80+
81+
self.project_name = os.environ['DJANGO_SETTINGS_MODULE'].split('.')[0]
82+
self.module = "%s.ajax" % self.app_name
83+
self.full_name = "%s.%s" % (self.module, self.method)
84+
85+
@staticmethod
86+
def get_js_functions():
87+
return dajaxice_functions.get_functions()
88+
89+
@staticmethod
90+
def get_media_prefix():
91+
return getattr(settings, 'DAJAXICE_MEDIA_PREFIX', "dajaxice")
92+
93+
@staticmethod
94+
def get_functions():
95+
return getattr(settings, 'DAJAXICE_FUNCTIONS', ())
96+
97+
@staticmethod
98+
def get_debug():
99+
return getattr(settings, 'DAJAXICE_DEBUG', True)
100+
101+
@staticmethod
102+
def get_notify_exceptions():
103+
return getattr(settings, 'DAJAXICE_NOTIFY_EXCEPTIONS', False)
104+
105+
@staticmethod
106+
def get_cache_control():
107+
if settings.DEBUG:
108+
return 0
109+
return getattr(settings, 'DAJAXICE_CACHE_CONTROL', 5 * 24 * 60 * 60)
110+
111+
@staticmethod
112+
def get_xmlhttprequest_js_import():
113+
return getattr(settings, 'DAJAXICE_XMLHTTPREQUEST_JS_IMPORT', True)
114+
115+
@staticmethod
116+
def get_json2_js_import():
117+
return getattr(settings, 'DAJAXICE_JSON2_JS_IMPORT', True)
118+
119+
@staticmethod
120+
def get_exception_message():
121+
return getattr(settings, 'DAJAXICE_EXCEPTION', u'DAJAXICE_EXCEPTION')
122+
123+
@staticmethod
124+
def get_js_docstrings():
125+
return getattr(settings, 'DAJAXICE_JS_DOCSTRINGS', False)
126+
127+
def _is_callable(self):
128+
"""
129+
Return if the request function was registered.
130+
"""
131+
return dajaxice_functions.is_callable(self.full_name)
132+
133+
def _get_ajax_function(self):
134+
"""
135+
Return a callable ajax function.
136+
This function should be imported according the Django version.
137+
"""
138+
return self._modern_get_ajax_function()
139+
140+
def _modern_get_ajax_function(self):
141+
"""
142+
Return a callable ajax function.
143+
This function uses django.utils.importlib
144+
"""
145+
self.module_import_name = "%s.%s" % (self.project_name, self.module)
146+
try:
147+
return self._modern_import()
148+
except:
149+
self.module_import_name = self.module
150+
return self._modern_import()
151+
152+
def _modern_import(self):
153+
try:
154+
mod = import_module(self.module_import_name)
155+
return mod.__getattribute__(self.method)
156+
except:
157+
raise DajaxiceImportError()
158+
159+
def process(self):
160+
"""
161+
Process the dajax request calling the apropiate method.
162+
"""
163+
if self._is_callable():
164+
log.debug('Function %s is callable' % self.full_name)
165+
166+
argv = self.request.POST.get('argv')
167+
if argv != 'undefined':
168+
try:
169+
argv = simplejson.loads(self.request.POST.get('argv'))
170+
argv = safe_dict(argv)
171+
except Exception, e:
172+
log.error('argv exception %s' % e)
173+
argv = {}
174+
else:
175+
argv = {}
176+
177+
log.debug('argv %s' % argv)
178+
179+
try:
180+
thefunction = self._get_ajax_function()
181+
response = '%s' % thefunction(self.request, **argv)
182+
183+
except Exception, e:
184+
trace = '\n'.join(traceback.format_exception(*sys.exc_info()))
185+
log.error(trace)
186+
response = '%s' % DajaxiceRequest.get_exception_message()
187+
188+
if DajaxiceRequest.get_notify_exceptions():
189+
self.notify_exception(self.request, sys.exc_info())
190+
191+
log.info('response: %s' % response)
192+
return HttpResponse(response, mimetype="application/x-json")
193+
194+
else:
195+
log.debug('Function %s is not callable' % self.full_name)
196+
raise FunctionNotCallableError(name=self.full_name)
197+
198+
def notify_exception(self, request, exc_info):
199+
"""
200+
Send Exception traceback to ADMINS
201+
Similar to BaseHandler.handle_uncaught_exception
202+
"""
203+
from django.conf import settings
204+
from django.core.mail import mail_admins
205+
206+
subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), request.path)
207+
try:
208+
request_repr = repr(request)
209+
except:
210+
request_repr = "Request repr() unavailable"
211+
212+
trace = '\n'.join(traceback.format_exception(*(exc_info or sys.exc_info())))
213+
message = "%s\n\n%s" % (trace, request_repr)
214+
mail_admins(subject, message, fail_silently=True)

0 commit comments

Comments
 (0)