diff --git a/dajaxice/__init__.py b/dajaxice/__init__.py new file mode 100644 index 000000000..3d2aa1c0e --- /dev/null +++ b/dajaxice/__init__.py @@ -0,0 +1 @@ +VERSION = (0, 2, 0, 0, 'beta') diff --git a/dajaxice/core/Dajaxice.py b/dajaxice/core/Dajaxice.py new file mode 100644 index 000000000..fabb62419 --- /dev/null +++ b/dajaxice/core/Dajaxice.py @@ -0,0 +1,181 @@ +import logging + +from django.conf import settings + +# Python 2.7 has an importlib with import_module. +# For older Pythons, Django's bundled copy provides it. +# For older Django's dajaxice reduced_import_module. +try: + from importlib import import_module +except: + try: + from django.utils.importlib import import_module + except: + from dajaxice.utils import simple_import_module as import_module + +log = logging.getLogger('dajaxice.DajaxiceRequest') + + +class DajaxiceFunction(object): + + def __init__(self, name, path, doc=None): + self.name = name + self.path = path + self.doc = doc + + def get_callable_path(self): + return '%s.%s' % (self.path.replace('.ajax', ''), self.name) + + def __cmp__(self, other): + return (self.name == other.name and self.path == other.path) + + +class DajaxiceModule(object): + def __init__(self, module): + self.functions = [] + self.sub_modules = [] + self.name = module[0] + + sub_module = module[1:] + if len(sub_module) != 0: + self.add_submodule(sub_module) + + def get_module(self, module): + """ + Recursively get_module util we found it. + """ + if len(module) == 0: + return self + + for dajaxice_module in self.sub_modules: + if dajaxice_module.name == module[0]: + return dajaxice_module.get_module(module[1:]) + return None + + def add_function(self, function): + self.functions.append(function) + + def has_sub_modules(self): + return len(self.sub_modules) > 0 + + def add_submodule(self, module): + """ + Recursively add_submodule, if it's not registered, create it. + """ + if len(module) == 0: + return + else: + sub_module = self.exist_submodule(module[0]) + + if type(sub_module) == int: + self.sub_modules[sub_module].add_submodule(module[1:]) + else: + self.sub_modules.append(DajaxiceModule(module)) + + def exist_submodule(self, name): + """ + Check if submodule name was already registered. + """ + for module in self.sub_modules: + if module.name == name: + return self.sub_modules.index(module) + return False + + +class Dajaxice(object): + def __init__(self): + self._registry = [] + self._callable = [] + + for function in getattr(settings, 'DAJAXICE_FUNCTIONS', ()): + function = function.rsplit('.', 1) + self.register_function(function[0], function[1]) + + def register(self, function): + self.register_function(function.__module__, function.__name__, function.__doc__) + + def register_function(self, module, name, doc=None): + """ + Register function at 'module' depth + """ + #Create the dajaxice function. + function = DajaxiceFunction(name=name, path=module, doc=doc) + + #Check for already registered functions. + full_path = '%s.%s' % (module, name) + if full_path in self._callable: + log.warning('%s already registered as dajaxice function.' % full_path) + return + + self._callable.append(full_path) + + #Dajaxice path without ajax. + module_without_ajax = module.replace('.ajax', '').split('.') + + #Register module if necessary. + exist_module = self._exist_module(module_without_ajax[0]) + + if type(exist_module) == int: + self._registry[exist_module].add_submodule(module_without_ajax[1:]) + else: + self._registry.append(DajaxiceModule(module_without_ajax)) + + #Register Function + module = self.get_module(module_without_ajax) + if module: + module.add_function(function) + + def get_module(self, module): + """ + Recursively get module from registry + """ + for dajaxice_module in self._registry: + if dajaxice_module.name == module[0]: + return dajaxice_module.get_module(module[1:]) + return None + + def is_callable(self, name): + return name in self._callable + + def _exist_module(self, module_name): + for module in self._registry: + if module.name == module_name: + return self._registry.index(module) + return False + + def get_functions(self): + return self._registry + + +LOADING_DAJAXICE = False + + +def dajaxice_autodiscover(): + """ + Auto-discover INSTALLED_APPS ajax.py modules and fail silently when + not present. + NOTE: dajaxice_autodiscover was inspired/copied from django.contrib.admin autodiscover + """ + global LOADING_DAJAXICE + if LOADING_DAJAXICE: + return + LOADING_DAJAXICE = True + + import imp + from django.conf import settings + + for app in settings.INSTALLED_APPS: + + try: + app_path = import_module(app).__path__ + except AttributeError: + continue + + try: + imp.find_module('ajax', app_path) + except ImportError: + continue + + import_module("%s.ajax" % app) + + LOADING_DAJAXICE = False diff --git a/dajaxice/core/DajaxiceRequest.py b/dajaxice/core/DajaxiceRequest.py new file mode 100644 index 000000000..8ce78ada1 --- /dev/null +++ b/dajaxice/core/DajaxiceRequest.py @@ -0,0 +1,214 @@ +#---------------------------------------------------------------------- +# Copyright (c) 2009-2011 Benito Jorge Bastida +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# o Redistributions of source code must retain the above copyright +# notice, this list of conditions, and the disclaimer that follows. +# +# o Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions, and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# o Neither the name of Digital Creations nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS +# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL +# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +#---------------------------------------------------------------------- + +import os +import sys +import logging +import traceback + +from django.conf import settings +from django.utils import simplejson +from django.http import HttpResponse + +from dajaxice.core import dajaxice_functions +from dajaxice.exceptions import FunctionNotCallableError, DajaxiceImportError + +log = logging.getLogger('dajaxice.DajaxiceRequest') + +# Python 2.7 has an importlib with import_module. +# For older Pythons, Django's bundled copy provides it. +# For older Django's dajaxice reduced_import_module. +try: + from importlib import import_module +except: + try: + from django.utils.importlib import import_module + except: + from dajaxice.utils import simple_import_module as import_module + + +def safe_dict(d): + """ + Recursively clone json structure with UTF-8 dictionary keys + http://www.gossamer-threads.com/lists/python/bugs/684379 + """ + if isinstance(d, dict): + return dict([(k.encode('utf-8'), safe_dict(v)) for k, v in d.iteritems()]) + elif isinstance(d, list): + return [safe_dict(x) for x in d] + else: + return d + + +class DajaxiceRequest(object): + + def __init__(self, request, call): + call = call.rsplit('.', 1) + self.app_name = call[0] + self.method = call[1] + self.request = request + + self.project_name = os.environ['DJANGO_SETTINGS_MODULE'].split('.')[0] + self.module = "%s.ajax" % self.app_name + self.full_name = "%s.%s" % (self.module, self.method) + + @staticmethod + def get_js_functions(): + return dajaxice_functions.get_functions() + + @staticmethod + def get_media_prefix(): + return getattr(settings, 'DAJAXICE_MEDIA_PREFIX', "dajaxice") + + @staticmethod + def get_functions(): + return getattr(settings, 'DAJAXICE_FUNCTIONS', ()) + + @staticmethod + def get_debug(): + return getattr(settings, 'DAJAXICE_DEBUG', True) + + @staticmethod + def get_notify_exceptions(): + return getattr(settings, 'DAJAXICE_NOTIFY_EXCEPTIONS', False) + + @staticmethod + def get_cache_control(): + if settings.DEBUG: + return 0 + return getattr(settings, 'DAJAXICE_CACHE_CONTROL', 5 * 24 * 60 * 60) + + @staticmethod + def get_xmlhttprequest_js_import(): + return getattr(settings, 'DAJAXICE_XMLHTTPREQUEST_JS_IMPORT', True) + + @staticmethod + def get_json2_js_import(): + return getattr(settings, 'DAJAXICE_JSON2_JS_IMPORT', True) + + @staticmethod + def get_exception_message(): + return getattr(settings, 'DAJAXICE_EXCEPTION', u'DAJAXICE_EXCEPTION') + + @staticmethod + def get_js_docstrings(): + return getattr(settings, 'DAJAXICE_JS_DOCSTRINGS', False) + + def _is_callable(self): + """ + Return if the request function was registered. + """ + return dajaxice_functions.is_callable(self.full_name) + + def _get_ajax_function(self): + """ + Return a callable ajax function. + This function should be imported according the Django version. + """ + return self._modern_get_ajax_function() + + def _modern_get_ajax_function(self): + """ + Return a callable ajax function. + This function uses django.utils.importlib + """ + self.module_import_name = "%s.%s" % (self.project_name, self.module) + try: + return self._modern_import() + except: + self.module_import_name = self.module + return self._modern_import() + + def _modern_import(self): + try: + mod = import_module(self.module_import_name) + return mod.__getattribute__(self.method) + except: + raise DajaxiceImportError() + + def process(self): + """ + Process the dajax request calling the apropiate method. + """ + if self._is_callable(): + log.debug('Function %s is callable' % self.full_name) + + argv = self.request.POST.get('argv') + if argv != 'undefined': + try: + argv = simplejson.loads(self.request.POST.get('argv')) + argv = safe_dict(argv) + except Exception, e: + log.error('argv exception %s' % e) + argv = {} + else: + argv = {} + + log.debug('argv %s' % argv) + + try: + thefunction = self._get_ajax_function() + response = '%s' % thefunction(self.request, **argv) + + except Exception, e: + trace = '\n'.join(traceback.format_exception(*sys.exc_info())) + log.error(trace) + response = '%s' % DajaxiceRequest.get_exception_message() + + if DajaxiceRequest.get_notify_exceptions(): + self.notify_exception(self.request, sys.exc_info()) + + log.info('response: %s' % response) + return HttpResponse(response, mimetype="application/x-json") + + else: + log.debug('Function %s is not callable' % self.full_name) + raise FunctionNotCallableError(name=self.full_name) + + def notify_exception(self, request, exc_info): + """ + Send Exception traceback to ADMINS + Similar to BaseHandler.handle_uncaught_exception + """ + from django.conf import settings + from django.core.mail import mail_admins + + subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), request.path) + try: + request_repr = repr(request) + except: + request_repr = "Request repr() unavailable" + + trace = '\n'.join(traceback.format_exception(*(exc_info or sys.exc_info()))) + message = "%s\n\n%s" % (trace, request_repr) + mail_admins(subject, message, fail_silently=True) diff --git a/dajaxice/core/__init__.py b/dajaxice/core/__init__.py new file mode 100644 index 000000000..e7ea9c1e3 --- /dev/null +++ b/dajaxice/core/__init__.py @@ -0,0 +1,5 @@ +from Dajaxice import Dajaxice +dajaxice_functions = Dajaxice() + +from DajaxiceRequest import DajaxiceRequest +from Dajaxice import dajaxice_autodiscover diff --git a/dajaxice/decorators.py b/dajaxice/decorators.py new file mode 100644 index 000000000..d3415449e --- /dev/null +++ b/dajaxice/decorators.py @@ -0,0 +1,10 @@ +from dajaxice.core import dajaxice_functions + + +def dajaxice_register(original_function): + """ + Register the original funcion and returns it + """ + + dajaxice_functions.register(original_function) + return original_function diff --git a/dajaxice/exceptions.py b/dajaxice/exceptions.py new file mode 100644 index 000000000..9ce08eee5 --- /dev/null +++ b/dajaxice/exceptions.py @@ -0,0 +1,41 @@ +#---------------------------------------------------------------------- +# Copyright (c) 2009-2011 Benito Jorge Bastida +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# o Redistributions of source code must retain the above copyright +# notice, this list of conditions, and the disclaimer that follows. +# +# o Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions, and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# o Neither the name of Digital Creations nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS +# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL +# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +#---------------------------------------------------------------------- + + +class FunctionNotCallableError(Exception): + def __init__(self, name): + self.name = name + + +class DajaxiceImportError(Exception): + pass diff --git a/dajaxice/finders.py b/dajaxice/finders.py new file mode 100644 index 000000000..f07416366 --- /dev/null +++ b/dajaxice/finders.py @@ -0,0 +1,75 @@ +import os +import tempfile + +from django.contrib.staticfiles import finders +from django.template import Context +from django.template.loader import get_template +from django.core.exceptions import SuspiciousOperation + + +class VirtualStorage(finders.FileSystemStorage): + """" Mock a FileSystemStorage to build tmp files on demand.""" + + def __init__(self, *args, **kwargs): + self._files_cache = {} + super(VirtualStorage, self).__init__(*args, **kwargs) + + def get_or_create_file(self, path): + if path not in self.files: + return '' + + data = getattr(self, self.files[path])() + + try: + current_file = open(self._files_cache[path]) + current_data = current_file.read() + current_file.close() + if current_data != data: + os.remove(path) + raise Exception("Invalid data") + except Exception: + handle, tmp_path = tempfile.mkstemp() + tmp_file = open(tmp_path, 'w') + tmp_file.write(data) + tmp_file.close() + self._files_cache[path] = tmp_path + + return self._files_cache[path] + + def exists(self, name): + return name in self.files + + def listdir(self, path): + folders, files = [], [] + for f in self.files: + if f.startswith(path): + f = f.replace(path, '', 1) + if os.sep in f: + folders.append(f.split(os.sep, 1)[0]) + else: + files.append(f) + return folders, files + + def path(self, name): + try: + path = self.get_or_create_file(name) + except ValueError: + raise SuspiciousOperation("Attempted access to '%s' denied." % name) + return os.path.normpath(path) + + +class DajaxiceStorage(VirtualStorage): + + files = {'dajaxice/dajaxice.core.js': 'dajaxice_core_js'} + + def dajaxice_core_js(self): + from dajaxice.core import dajaxice_autodiscover, dajaxice_config + + dajaxice_autodiscover() + + c = Context({'dajaxice_config': dajaxice_config}) + return get_template('dajaxice/dajaxice.core.js').render(c) + + +class DajaxiceFinder(finders.BaseStorageFinder): + storage = DajaxiceStorage() diff --git a/dajaxice/management/__init__.py b/dajaxice/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/dajaxice/management/commands/__init__.py b/dajaxice/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/dajaxice/management/commands/generate_static_dajaxice.py b/dajaxice/management/commands/generate_static_dajaxice.py new file mode 100644 index 000000000..0edc92444 --- /dev/null +++ b/dajaxice/management/commands/generate_static_dajaxice.py @@ -0,0 +1,83 @@ +#---------------------------------------------------------------------- +# Copyright (c) 2009-2011 Benito Jorge Bastida +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# o Redistributions of source code must retain the above copyright +# notice, this list of conditions, and the disclaimer that follows. +# +# o Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions, and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# o Neither the name of Digital Creations nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS +# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL +# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +#---------------------------------------------------------------------- + +import httplib +import urllib + +from django.core.management.base import BaseCommand +from django.template.loader import render_to_string +from optparse import make_option + +from dajaxice.core import DajaxiceRequest +from dajaxice.core import dajaxice_autodiscover +dajaxice_autodiscover() + + +class Command(BaseCommand): + help = "Generate dajaxice.core.js file to import it as static file" + args = "[--compile]" + option_list = BaseCommand.option_list + ( + make_option('--compile', default='no', dest='compile', help='Compile output using Google closure-compiler'), + ) + + requires_model_validation = False + + def handle(self, *app_labels, **options): + compile_output = options.get('compile', 'yes') + data = {'dajaxice_js_functions': DajaxiceRequest.get_js_functions(), + 'DAJAXICE_URL_PREFIX': DajaxiceRequest.get_media_prefix(), + 'DAJAXICE_XMLHTTPREQUEST_JS_IMPORT': DajaxiceRequest.get_xmlhttprequest_js_import(), + 'DAJAXICE_JSON2_JS_IMPORT': DajaxiceRequest.get_json2_js_import(), + 'DAJAXICE_EXCEPTION': DajaxiceRequest.get_exception_message()} + + js = render_to_string('dajaxice/dajaxice.core.js', data) + if compile_output.lower() == "closure": + print self.complie_js_with_closure(js) + else: + print js + + def complie_js_with_closure(self, js): + params = urllib.urlencode([ + ('js_code', js), + ('compilation_level', 'ADVANCED_OPTIMIZATIONS'), + ('output_format', 'text'), + ('output_info', 'compiled_code'), + ]) + # Always use the following value for the Content-type header. + headers = {"Content-type": "application/x-www-form-urlencoded"} + conn = httplib.HTTPConnection('closure-compiler.appspot.com') + conn.request('POST', '/compile', params, headers) + response = conn.getresponse() + data = response.read() + conn.close() + return data diff --git a/dajaxice/models.py b/dajaxice/models.py new file mode 100644 index 000000000..e69de29bb diff --git a/dajaxice/templates/dajaxice/dajaxice.core.js b/dajaxice/templates/dajaxice/dajaxice.core.js new file mode 100644 index 000000000..d4b92648d --- /dev/null +++ b/dajaxice/templates/dajaxice/dajaxice.core.js @@ -0,0 +1,128 @@ +var Dajaxice = { + {% for module in dajaxice_js_functions %} + {% include "dajaxice/dajaxice_core_loop.js" %} + {% endfor %}{% ifnotequal dajaxice_js_functions|length 0 %},{% endifnotequal %} + + get_cookie: function(name) + { + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i].toString().replace(/^\s+/, "").replace(/\s+$/, ""); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + }, + + call: function(dajaxice_function, dajaxice_callback, argv, exception_callback) + { + var send_data = []; + var is_callback_a_function = (typeof(dajaxice_callback) == 'function'); + if(!is_callback_a_function){ + alert("dajaxice_callback should be a function since dajaxice 0.2") + } + + if(exception_callback==undefined || typeof(dajaxice_callback) != 'function'){ + exception_callback = this.get_setting('default_exception_callback'); + } + + send_data.push('argv='+encodeURIComponent(JSON.stringify(argv))); + send_data = send_data.join('&'); + var oXMLHttpRequest = new XMLHttpRequest; + oXMLHttpRequest.open('POST', '/{{DAJAXICE_URL_PREFIX}}/'+dajaxice_function+'/'); + oXMLHttpRequest.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + oXMLHttpRequest.setRequestHeader("X-CSRFToken",Dajaxice.get_cookie('csrftoken')); + oXMLHttpRequest.onreadystatechange = function() { + if (this.readyState == XMLHttpRequest.DONE) { + if(this.responseText == Dajaxice.EXCEPTION){ + exception_callback(); + } + else{ + try{ + dajaxice_callback(JSON.parse(this.responseText)); + } + catch(exception){ + dajaxice_callback(this.responseText); + } + } + } + } + oXMLHttpRequest.send(send_data); + }, + + setup: function(settings) + { + this.settings = settings; + }, + + get_setting: function(key){ + if(this.settings == undefined || this.settings[key] == undefined){ + return this.default_settings[key]; + } + return this.settings[key]; + }, + + default_exception_callback: function(data){ + alert('Something goes wrong'); + } +}; + +Dajaxice.EXCEPTION = '{{ DAJAXICE_EXCEPTION }}'; +Dajaxice.default_settings = {'default_exception_callback': Dajaxice.default_exception_callback} + +window['Dajaxice'] = Dajaxice; + +{% comment %} +/* + XMLHttpRequest.js Compiled with Google Closure + + XMLHttpRequest.js Copyright (C) 2008 Sergey Ilinsky (http://www.ilinsky.com) + + This work is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This work is distributed in the hope that it will be useful, + but without any warranty; without even the implied warranty of + merchantability or fitness for a particular purpose. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, write to the Free Software Foundation, Inc., + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +{% endcomment %} +{% if DAJAXICE_XMLHTTPREQUEST_JS_IMPORT %} +(function(){function b(){this._object=i?new i:new window.ActiveXObject("Microsoft.XMLHTTP");this._listeners=[]}function k(a){b.onreadystatechange&&b.onreadystatechange.apply(a);a.dispatchEvent({type:"readystatechange",bubbles:false,cancelable:false,timeStamp:new Date+0})}function p(a){var c=a.responseXML,d=a.responseText;if(h&&d&&c&&!c.documentElement&&a.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)){c=new window.ActiveXObject("Microsoft.XMLDOM");c.async=false;c.validateOnParse=false; +c.loadXML(d)}if(c)if(h&&c.parseError!=0||!c.documentElement||c.documentElement&&c.documentElement.tagName=="parsererror")return null;return c}function o(a){try{a.responseText=a._object.responseText}catch(c){}try{a.responseXML=p(a._object)}catch(d){}try{a.status=a._object.status}catch(g){}try{a.statusText=a._object.statusText}catch(e){}}function l(a){a._object.onreadystatechange=new window.Function}var i=window.XMLHttpRequest,j=!!window.controllers,h=window.document.all&&!window.opera;if(j&&i.wrapped)b.wrapped= +i.wrapped;b.UNSENT=0;b.OPENED=1;b.HEADERS_RECEIVED=2;b.LOADING=3;b.DONE=4;b.prototype.readyState=b.UNSENT;b.prototype.responseText="";b.prototype.responseXML=null;b.prototype.status=0;b.prototype.statusText="";b.prototype.onreadystatechange=null;b.onreadystatechange=null;b.onopen=null;b.onsend=null;b.onabort=null;b.prototype.open=function(a,c,d,g,e){delete this._headers;if(arguments.length<3)d=true;this._async=d;var f=this,m=this.readyState,n;if(h&&d){n=function(){if(m!=b.DONE){l(f);f.abort()}};window.attachEvent("onunload", +n)}b.onopen&&b.onopen.apply(this,arguments);if(arguments.length>4)this._object.open(a,c,d,g,e);else arguments.length>3?this._object.open(a,c,d,g):this._object.open(a,c,d);if(!j&&!h){this.readyState=b.OPENED;k(this)}this._object.onreadystatechange=function(){if(!(j&&!d)){f.readyState=f._object.readyState;o(f);if(f._aborted)f.readyState=b.UNSENT;else{if(f.readyState==b.DONE){l(f);h&&d&&window.detachEvent("onunload",n)}m!=f.readyState&&k(f);m=f.readyState}}}};b.prototype.send=function(a){b.onsend&&b.onsend.apply(this, +arguments);if(a&&a.nodeType){a=window.XMLSerializer?(new window.XMLSerializer).serializeToString(a):a.xml;this._headers["Content-Type"]||this._object.setRequestHeader("Content-Type","application/xml")}this._object.send(a);if(j&&!this._async){this.readyState=b.OPENED;for(o(this);this.readyState<b.DONE;){this.readyState++;k(this);if(this._aborted)return}}};b.prototype.abort=function(){b.onabort&&b.onabort.apply(this,arguments);if(this.readyState>b.UNSENT)this._aborted=true;this._object.abort();l(this)}; +b.prototype.getAllResponseHeaders=function(){return this._object.getAllResponseHeaders()};b.prototype.getResponseHeader=function(a){return this._object.getResponseHeader(a)};b.prototype.setRequestHeader=function(a,c){if(!this._headers)this._headers={};this._headers[a]=c;return this._object.setRequestHeader(a,c)};b.prototype.addEventListener=function(a,c,d){for(var g=0,e;e=this._listeners[g];g++)if(e[0]==a&&e[1]==c&&e[2]==d)return;this._listeners.push([a,c,d])};b.prototype.removeEventListener=function(a, +c,d){for(var g=0,e;e=this._listeners[g];g++)if(e[0]==a&&e[1]==c&&e[2]==d)break;e&&this._listeners.splice(g,1)};b.prototype.dispatchEvent=function(a){a={type:a.type,target:this,currentTarget:this,eventPhase:2,bubbles:a.bubbles,cancelable:a.cancelable,timeStamp:a.timeStamp,stopPropagation:function(){},preventDefault:function(){},initEvent:function(){}};if(a.type=="readystatechange"&&this.onreadystatechange)(this.onreadystatechange.handleEvent||this.onreadystatechange).apply(this,[a]);for(var c=0,d;d= +this._listeners[c];c++)if(d[0]==a.type&&!d[2])(d[1].handleEvent||d[1]).apply(this,[a])};b.prototype.toString=function(){return"[object XMLHttpRequest]"};b.toString=function(){return"[XMLHttpRequest]"};if(!window.Function.prototype.apply)window.Function.prototype.apply=function(a,c){c||(c=[]);a.__func=this;a.__func(c[0],c[1],c[2],c[3],c[4]);delete a.__func};window.XMLHttpRequest=b})(); +{% endif %} + +{% comment %} +/* + http://www.json.org/json2.js Compiled with Google Closure + This code was released under Public Domain by json.org , Thanks! + I hope that in the future this code won't be neccessary, but today all browsers doesn't supports JSON.stringify(). +*/ +{% endcomment %} +{% if DAJAXICE_JSON2_JS_IMPORT %} +if(!this.JSON)this.JSON={}; +(function(){function k(a){return a<10?"0"+a:a}function n(a){o.lastIndex=0;return o.test(a)?'"'+a.replace(o,function(c){var d=q[c];return typeof d==="string"?d:"\\u"+("0000"+c.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function l(a,c){var d,f,i=g,e,b=c[a];if(b&&typeof b==="object"&&typeof b.toJSON==="function")b=b.toJSON(a);if(typeof j==="function")b=j.call(c,a,b);switch(typeof b){case "string":return n(b);case "number":return isFinite(b)?String(b):"null";case "boolean":case "null":return String(b);case "object":if(!b)return"null"; +g+=m;e=[];if(Object.prototype.toString.apply(b)==="[object Array]"){f=b.length;for(a=0;a<f;a+=1)e[a]=l(a,b)||"null";c=e.length===0?"[]":g?"[\n"+g+e.join(",\n"+g)+"\n"+i+"]":"["+e.join(",")+"]";g=i;return c}if(j&&typeof j==="object"){f=j.length;for(a=0;a<f;a+=1){d=j[a];if(typeof d==="string")if(c=l(d,b))e.push(n(d)+(g?": ":":")+c)}}else for(d in b)if(Object.hasOwnProperty.call(b,d))if(c=l(d,b))e.push(n(d)+(g?": ":":")+c);c=e.length===0?"{}":g?"{\n"+g+e.join(",\n"+g)+"\n"+i+"}":"{"+e.join(",")+"}"; +g=i;return c}}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+k(this.getUTCMonth()+1)+"-"+k(this.getUTCDate())+"T"+k(this.getUTCHours())+":"+k(this.getUTCMinutes())+":"+k(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()}}var p=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, +o=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,g,m,q={"\u0008":"\\b","\t":"\\t","\n":"\\n","\u000c":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},j;if(typeof JSON.stringify!=="function")JSON.stringify=function(a,c,d){var f;m=g="";if(typeof d==="number")for(f=0;f<d;f+=1)m+=" ";else if(typeof d==="string")m=d;if((j=c)&&typeof c!=="function"&&(typeof c!=="object"||typeof c.length!=="number"))throw new Error("JSON.stringify");return l("", +{"":a})};if(typeof JSON.parse!=="function")JSON.parse=function(a,c){function d(f,i){var e,b,h=f[i];if(h&&typeof h==="object")for(e in h)if(Object.hasOwnProperty.call(h,e)){b=d(h,e);if(b!==undefined)h[e]=b;else delete h[e]}return c.call(f,i,h)}p.lastIndex=0;if(p.test(a))a=a.replace(p,function(f){return"\\u"+("0000"+f.charCodeAt(0).toString(16)).slice(-4)});if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, +"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){a=eval("("+a+")");return typeof c==="function"?d({"":a},""):a}throw new SyntaxError("JSON.parse");}})(); +{% endif %} \ No newline at end of file diff --git a/dajaxice/templates/dajaxice/dajaxice_core_loop.js b/dajaxice/templates/dajaxice/dajaxice_core_loop.js new file mode 100644 index 000000000..729804883 --- /dev/null +++ b/dajaxice/templates/dajaxice/dajaxice_core_loop.js @@ -0,0 +1,16 @@ +{{ module.name }}: { + {% for function in module.functions %} + {% if function.doc and DAJAXICE_JS_DOCSTRINGS %}/* {{ function.doc|default:'' }}*/ {% endif %} + {{ function.name }}: function(callback_function, argv, exception_callback){ + Dajaxice.call('{{function.get_callable_path}}', callback_function, argv, exception_callback); + }{% if not forloop.last %},{% endif %} + {% endfor %} + + {% for sub_module in module.sub_modules %} + {% with "dajaxice/dajaxice_core_loop.js" as filename %} + {% with sub_module as module %} + {% include filename %} + {% endwith %} + {% endwith %} + {% endfor %} + }{% if not forloop.last %},{% endif %} \ No newline at end of file diff --git a/dajaxice/templates/dajaxice/dajaxice_function_loop.js b/dajaxice/templates/dajaxice/dajaxice_function_loop.js new file mode 100644 index 000000000..092d9a49e --- /dev/null +++ b/dajaxice/templates/dajaxice/dajaxice_function_loop.js @@ -0,0 +1,5 @@ +{% for function_name, function in module.functions.items %} + {{ function_name }}: function(callback_function, argv, custom_settings){ + Dajaxice.call('{{ function.name }}', '{{ function.method }}', callback_function, argv, custom_settings); + }{% if not forloop.last or top %},{% endif %} +{% endfor %} diff --git a/dajaxice/templates/dajaxice/dajaxice_js_import.html b/dajaxice/templates/dajaxice/dajaxice_js_import.html new file mode 100644 index 000000000..9fb2a5e7d --- /dev/null +++ b/dajaxice/templates/dajaxice/dajaxice_js_import.html @@ -0,0 +1 @@ +<script src="/{{ DAJAXICE_MEDIA_PREFIX }}/dajaxice.core.js" type="text/javascript" charset="utf-8"></script> \ No newline at end of file diff --git a/dajaxice/templates/dajaxice/dajaxice_module_loop.js b/dajaxice/templates/dajaxice/dajaxice_module_loop.js new file mode 100644 index 000000000..3abadbc8c --- /dev/null +++ b/dajaxice/templates/dajaxice/dajaxice_module_loop.js @@ -0,0 +1,8 @@ +{{ name }}: { + {% include "dajaxice/dajaxice_function_loop.js" %} + {% for name, sub_module in module.submodules.items %} + {% with filename="dajaxice/dajaxice_module_loop.js" module=sub_module %} + {% include filename %} + {% endwith %} + {% endfor %} + }{% if not forloop.last %},{% endif %} diff --git a/dajaxice/templatetags/__init__.py b/dajaxice/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/dajaxice/templatetags/dajaxice_templatetags.py b/dajaxice/templatetags/dajaxice_templatetags.py new file mode 100644 index 000000000..9c622d142 --- /dev/null +++ b/dajaxice/templatetags/dajaxice_templatetags.py @@ -0,0 +1,42 @@ +#---------------------------------------------------------------------- +# Copyright (c) 2009-2011 Benito Jorge Bastida +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# o Redistributions of source code must retain the above copyright +# notice, this list of conditions, and the disclaimer that follows. +# +# o Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions, and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# o Neither the name of Digital Creations nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS +# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL +# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +#---------------------------------------------------------------------- + +from django import template +from dajaxice.core import DajaxiceRequest + +register = template.Library() + + +@register.inclusion_tag('dajaxice/dajaxice_js_import.html', takes_context=True) +def dajaxice_js_import(context): + return {'DAJAXICE_MEDIA_PREFIX': DajaxiceRequest.get_media_prefix()} diff --git a/dajaxice/tests/__init__.py b/dajaxice/tests/__init__.py new file mode 100644 index 000000000..74723db14 --- /dev/null +++ b/dajaxice/tests/__init__.py @@ -0,0 +1,174 @@ +#---------------------------------------------------------------------- +# Copyright (c) 2009-2011 Benito Jorge Bastida +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# o Redistributions of source code must retain the above copyright +# notice, this list of conditions, and the disclaimer that follows. +# +# o Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions, and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# o Neither the name of Digital Creations nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS +# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL +# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +#---------------------------------------------------------------------- + +import os +import unittest + +from django.test import TestCase +from django.conf import settings + +from dajaxice.exceptions import FunctionNotCallableError, DajaxiceImportError +from dajaxice.core import DajaxiceRequest +from dajaxice.core.Dajaxice import Dajaxice, DajaxiceModule, DajaxiceFunction +from dajaxice.core import dajaxice_functions + + +class DjangoIntegrationTest(TestCase): + + urls = 'dajaxice.tests.urls' + + def setUp(self): + settings.DAJAXICE_MEDIA_PREFIX = "dajaxice" + settings.DAJAXICE_DEBUG = False + settings.INSTALLED_APPS += ('dajaxice.tests', 'dajaxice.tests.submodules',) + os.environ['DJANGO_SETTINGS_MODULE'] = 'dajaxice' + + def test_calling_not_registered_function(self): + self.failUnlessRaises(FunctionNotCallableError, self.client.post, '/dajaxice/dajaxice.tests.this_function_not_exist/', {'callback': 'my_callback'}) + + def test_calling_registered_function(self): + response = self.client.post('/dajaxice/dajaxice.tests.test_foo/', {'callback': 'my_callback'}) + + self.failUnlessEqual(response.status_code, 200) + self.failUnlessEqual(response.content, 'my_callback()') + + def test_calling_registered_function_with_params(self): + + response = self.client.post('/dajaxice/dajaxice.tests.test_foo_with_params/', {'callback': 'my_callback', 'argv': '{"param1":"value1"}'}) + + self.failUnlessEqual(response.status_code, 200) + self.failUnlessEqual(response.content, 'my_callback("value1")') + + def test_bad_function(self): + + response = self.client.post('/dajaxice/dajaxice.tests.test_ajax_exception/', {'callback': 'my_callback'}) + self.failUnlessEqual(response.status_code, 200) + self.failUnlessEqual(response.content, "my_callback('DAJAXICE_EXCEPTION')") + + def test_is_callable(self): + + dr = DajaxiceRequest(None, 'dajaxice.tests.test_registered_function') + self.failUnless(dr._is_callable()) + + dr = DajaxiceRequest(None, 'dajaxice.tests.test_ajax_not_registered') + self.failIf(dr._is_callable()) + + def test_get_js_functions(self): + + js_functions = DajaxiceRequest.get_js_functions() + + functions = [DajaxiceFunction('test_registered_function', 'dajaxice.tests.ajax.test_registered_function'), + DajaxiceFunction('test_string', 'dajaxice.tests.ajax.test_string'), + DajaxiceFunction('test_ajax_exception', 'dajaxice.tests.ajax.test_ajax_exception'), + DajaxiceFunction('test_foo', 'dajaxice.tests.ajax.test_foo'), + DajaxiceFunction('test_foo_with_params', 'dajaxice.tests.ajax.test_foo_with_params'), + DajaxiceFunction('test_submodule_registered_function', 'dajaxice.tests.submodules.ajax.test_submodule_registered_function')] + + callables = [f.path for f in functions] + + self.failUnlessEqual(len(js_functions), 1) + self.failUnlessEqual(dajaxice_functions._callable, callables) + + sub = js_functions[0] + self.failUnlessEqual(len(sub.sub_modules), 1) + self.failUnlessEqual(len(sub.functions), 0) + self.failUnlessEqual(sub.name, 'dajaxice') + + sub = js_functions[0].sub_modules[0] + self.failUnlessEqual(len(sub.sub_modules), 1) + self.failUnlessEqual(len(sub.functions), 5) + self.failUnlessEqual(sub.functions, functions[:-1]) + self.failUnlessEqual(sub.name, 'tests') + + sub = js_functions[0].sub_modules[0].sub_modules[0] + self.failUnlessEqual(len(sub.sub_modules), 0) + self.failUnlessEqual(len(sub.functions), 1) + self.failUnlessEqual(sub.functions, functions[-1:]) + self.failUnlessEqual(sub.name, 'submodules') + + def test_get_ajax_function(self): + + # Test modern Import with a real ajax function + dr = DajaxiceRequest(None, 'dajaxice.tests.test_foo') + function = dr._modern_get_ajax_function() + self.failUnless(hasattr(function, '__call__')) + + # Test modern Import without a real ajax function + dr = DajaxiceRequest(None, 'dajaxice.tests.test_foo2') + self.failUnlessRaises(DajaxiceImportError, dr._modern_get_ajax_function) + + +class DajaxiceFunctionTest(unittest.TestCase): + + def setUp(self): + self.function = DajaxiceFunction('my_function', 'module.submodule.foo.ajax') + + def test_constructor(self): + self.failUnlessEqual(self.function.name, 'my_function') + self.failUnlessEqual(self.function.path, 'module.submodule.foo.ajax') + + def test_get_callable_path(self): + self.failUnlessEqual(self.function.get_callable_path(), 'module.submodule.foo.my_function') + + +class DajaxiceModuleTest(unittest.TestCase): + + def setUp(self): + self.module = DajaxiceModule('module.submodule.foo.ajax'.split('.')) + + def test_constructor(self): + self.failUnlessEqual(self.module.functions, []) + self.failUnlessEqual(self.module.name, 'module') + + self.failUnlessEqual(len(self.module.sub_modules), 1) + + def test_add_function(self): + function = DajaxiceFunction('my_function', 'module.submodule.foo.ajax') + self.failUnlessEqual(len(self.module.functions), 0) + self.module.add_function(function) + self.failUnlessEqual(len(self.module.functions), 1) + + def test_has_sub_modules(self): + self.failUnlessEqual(self.module.has_sub_modules(), True) + + def test_exist_submodule(self): + self.failUnlessEqual(self.module.exist_submodule('submodule'), 0) + self.assertFalse(self.module.exist_submodule('other')) + self.module.add_submodule('other.foo'.split('.')) + self.failUnlessEqual(self.module.exist_submodule('other'), 1) + + def test_add_submodule(self): + self.failUnlessEqual(len(self.module.sub_modules), 1) + self.module.add_submodule('other.foo'.split('.')) + self.failUnlessEqual(len(self.module.sub_modules), 2) + self.assertTrue(type(self.module.sub_modules[1]), DajaxiceModule) diff --git a/dajaxice/tests/ajax.py b/dajaxice/tests/ajax.py new file mode 100644 index 000000000..c0a27fb91 --- /dev/null +++ b/dajaxice/tests/ajax.py @@ -0,0 +1,61 @@ +#---------------------------------------------------------------------- +# Copyright (c) 2009-2011 Benito Jorge Bastida +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# o Redistributions of source code must retain the above copyright +# notice, this list of conditions, and the disclaimer that follows. +# +# o Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions, and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# o Neither the name of Digital Creations nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS +# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL +# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +#---------------------------------------------------------------------- + +from django.utils import simplejson +from dajaxice.core import dajaxice_functions + + +def test_registered_function(request): + return "" +dajaxice_functions.register(test_registered_function) + + +def test_string(request): + return simplejson.dumps({'string': 'hello world'}) +dajaxice_functions.register(test_string) + + +def test_ajax_exception(request): + raise Exception() + return +dajaxice_functions.register(test_ajax_exception) + + +def test_foo(request): + return "" +dajaxice_functions.register(test_foo) + + +def test_foo_with_params(request, param1): + return simplejson.dumps(param1) +dajaxice_functions.register(test_foo_with_params) diff --git a/dajaxice/tests/requirements.txt b/dajaxice/tests/requirements.txt new file mode 100644 index 000000000..94a0e8344 --- /dev/null +++ b/dajaxice/tests/requirements.txt @@ -0,0 +1 @@ +Django diff --git a/dajaxice/tests/submodules/__init__.py b/dajaxice/tests/submodules/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/dajaxice/tests/submodules/ajax.py b/dajaxice/tests/submodules/ajax.py new file mode 100644 index 000000000..d3028347c --- /dev/null +++ b/dajaxice/tests/submodules/ajax.py @@ -0,0 +1,40 @@ +#---------------------------------------------------------------------- +# Copyright (c) 2009-2011 Benito Jorge Bastida +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# o Redistributions of source code must retain the above copyright +# notice, this list of conditions, and the disclaimer that follows. +# +# o Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions, and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# o Neither the name of Digital Creations nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS +# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL +# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +#---------------------------------------------------------------------- + + +from dajaxice.core import dajaxice_functions + + +def test_submodule_registered_function(request): + return "" +dajaxice_functions.register(test_submodule_registered_function) diff --git a/dajaxice/tests/urls.py b/dajaxice/tests/urls.py new file mode 100644 index 000000000..fc4a15050 --- /dev/null +++ b/dajaxice/tests/urls.py @@ -0,0 +1,44 @@ +#---------------------------------------------------------------------- +# Copyright (c) 2009-2011 Benito Jorge Bastida +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# o Redistributions of source code must retain the above copyright +# notice, this list of conditions, and the disclaimer that follows. +# +# o Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions, and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# o Neither the name of Digital Creations nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS +# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL +# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +#---------------------------------------------------------------------- + +from django.conf.urls.defaults import * +from django.conf import settings +from dajaxice.core import dajaxice_autodiscover + + +dajaxice_autodiscover() + +urlpatterns = patterns('', + #Dajaxice URLS + (r'^%s/' % settings.DAJAXICE_MEDIA_PREFIX, include('dajaxice.urls')), +) diff --git a/dajaxice/urls.py b/dajaxice/urls.py new file mode 100644 index 000000000..b86090c0b --- /dev/null +++ b/dajaxice/urls.py @@ -0,0 +1,39 @@ +#---------------------------------------------------------------------- +# Copyright (c) 2009-2011 Benito Jorge Bastida +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# o Redistributions of source code must retain the above copyright +# notice, this list of conditions, and the disclaimer that follows. +# +# o Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions, and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# o Neither the name of Digital Creations nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS +# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL +# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +#---------------------------------------------------------------------- + +from django.conf.urls.defaults import * + + +urlpatterns = patterns('', + url(r'^dajaxice.core.js$', 'dajaxice.views.js_core'), + url(r'^(.*)/$', 'dajaxice.views.dajaxice_request'),) diff --git a/dajaxice/utils.py b/dajaxice/utils.py new file mode 100644 index 000000000..f2bd976ee --- /dev/null +++ b/dajaxice/utils.py @@ -0,0 +1,50 @@ +#---------------------------------------------------------------------- +# Copyright (c) 2009-2011 Benito Jorge Bastida +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# o Redistributions of source code must retain the above copyright +# notice, this list of conditions, and the disclaimer that follows. +# +# o Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions, and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# o Neither the name of Digital Creations nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS +# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL +# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +#---------------------------------------------------------------------- + + +def deserialize_form(data): + """ + Create a new QueryDict from a serialized form. + """ + from django.http import QueryDict + data = QueryDict(query_string=unicode(data).encode('utf-8')) + return data + + +def simple_import_module(name): + """ + Reduced version of import_module + """ + import sys + __import__(name) + return sys.modules[name] diff --git a/dajaxice/views.py b/dajaxice/views.py new file mode 100644 index 000000000..f6ee83c61 --- /dev/null +++ b/dajaxice/views.py @@ -0,0 +1,62 @@ +#---------------------------------------------------------------------- +# Copyright (c) 2009-2011 Benito Jorge Bastida +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# o Redistributions of source code must retain the above copyright +# notice, this list of conditions, and the disclaimer that follows. +# +# o Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions, and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# o Neither the name of Digital Creations nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS +# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL +# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +#---------------------------------------------------------------------- + +from django.shortcuts import render_to_response +from django.views.decorators.cache import cache_control + +from dajaxice.core import DajaxiceRequest + + +def dajaxice_request(request, call): + """ + dajaxice_request + Uses DajaxRequest to handle dajax request. + Return the apropiate json according app_name and method. + """ + return DajaxiceRequest(request, call).process() + + +@cache_control(max_age=DajaxiceRequest.get_cache_control()) +def js_core(request): + """ + Return the dajax JS code according settings.DAJAXICE_FUNCTIONS + registered functions. + """ + data = {'dajaxice_js_functions': DajaxiceRequest.get_js_functions(), + 'DAJAXICE_URL_PREFIX': DajaxiceRequest.get_media_prefix(), + 'DAJAXICE_XMLHTTPREQUEST_JS_IMPORT': DajaxiceRequest.get_xmlhttprequest_js_import(), + 'DAJAXICE_JSON2_JS_IMPORT': DajaxiceRequest.get_json2_js_import(), + 'DAJAXICE_EXCEPTION': DajaxiceRequest.get_exception_message(), + 'DAJAXICE_JS_DOCSTRINGS': DajaxiceRequest.get_js_docstrings()} + + return render_to_response('dajaxice/dajaxice.core.js', data, mimetype="text/javascript") diff --git a/ietf/settings.py b/ietf/settings.py index c66b6187d..01dc0e836 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -87,6 +87,9 @@ MEDIA_URL = '' # Examples: "http://foo.com/media/", "/media/". ADMIN_MEDIA_PREFIX = '/media/' +#DAJAXICE_MEDIA_PREFIX="dajaxice" +DAJAXICE_MEDIA_PREFIX="" + AUTH_PROFILE_MODULE = 'person.Person' AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.RemoteUserBackend', ) diff --git a/ietf/urls.py b/ietf/urls.py index 4d44307e6..f91e2d042 100644 --- a/ietf/urls.py +++ b/ietf/urls.py @@ -21,6 +21,10 @@ from django.conf import settings admin.autodiscover() admin.site.disable_action('delete_selected') + +from dajaxice.core import dajaxice_autodiscover +dajaxice_autodiscover() + feeds = { 'iesg-agenda': IESGAgenda, 'last-call': InLastCall, @@ -76,6 +80,7 @@ urlpatterns = patterns('', # Google webmaster tools verification url (r'^googlea30ad1dacffb5e5b.html', 'django.views.generic.simple.direct_to_template', { 'template': 'googlea30ad1dacffb5e5b.html' }), + (r'^%s/' % settings.DAJAXICE_MEDIA_PREFIX, include('dajaxice.urls')), ) if settings.SERVER_MODE in ('development', 'test'):