"""Integrates Dojo with Django.""" import json import uuid # Used to convert Django Field attributes to Dojo attributes. # # Key is Django Field attribute name. # Value is tuple. # [0] == Dijit attribute name # [1] == callable to convert Django value to Dijit value default_attr_map = { 'required': ('required', None), 'min_length': ('minLength', None), 'max_length': ('maxLength', None), 'regex': ('regExp', lambda a: a.pattern) } def dojo_field(field, dojo_type, attr_map=None, dojo_attrs=None): """ Instruments a Django form.Field with Dijit parameters. arguments ========== * field - Django form.Field to instrument. * dojo_type - str, qualified class name of dijit class to use. * attr_map - dict, optional mapping between Django and Dojo attrs, see default_attr_map comments for format. * dojo_attrs - dict, Attributes to apply to Dijit. """ # Specifies mapping between Django field attributes # and Dijit attributes if attr_map is not None: map = dict(default_attr_map) map.update(attr_map) else: map = default_attr_map # Get mapped attributes from Django field attrs = {} for attr, mapper in map.iteritems(): val = getattr(field, attr, None) if val is None: continue if mapper[1] is not None: val = mapper[1](val) attrs[mapper[0]] = val # Convert Error message error_msgs = getattr(field, 'error_messages', None) if error_msgs is not None: invalid_msg = error_msgs.get('invalid', None) if invalid_msg is not None: # Django uses a # lazy proxy when this property is not # explicitly set. json encoder will choke # on the proxy object. Calling a method # causes the proxy to be evaluated and # json encoder is happy. attrs['invalidMessage'] = invalid_msg.rstrip() # Add user defined attributes if dojo_attrs is not None: attrs.update(dojo_attrs) # Attach Dojo attributes to field field._dojo_type = dojo_type field._dojo_params = attrs class Dojo(object): """Keeps track of Dojo properties.""" def __init__(self, src, enabled=True, theme='tundra', dj_config=None, form_function=None): self.enabled = enabled self.dj_config = dj_config self.form_function = form_function self._src = src self._stylesheets = ['/'.join((self._src, 'dojo', 'resources', 'dojo.css'))] self._modules = [] #required_modules self._aols = [] # addOnLoad functions self.theme = theme def get_src(self): return self._src src = property(get_src) def get_theme(self): return self._theme def set_theme(self, val): def _get_theme_css(theme): return '/'.join((self._src, 'dijit', 'themes', theme, '%s.css' % theme)) current_theme = getattr(self, '_theme', None) if current_theme is not None: theme_css = _get_theme_css(current_theme) if theme_css in self._stylesheets: self._stylesheets.remove(theme_css) self._theme = val theme_css = _get_theme_css(self._theme) self.append_stylesheet(theme_css) theme = property(get_theme, set_theme) def get_modules(self): return self._modules def set_modules(self, val): self._modules = val modules = property(get_modules, set_modules) def get_stylesheets(self): return self._stylesheets def set_stylesheets(self, val): self._stylesheets = val stylesheets = property(get_stylesheets, set_stylesheets) def get_aols(self): return self._aols def set_aols(self, val): self._aols = val aols = property(get_aols, set_aols) def set_module_path(self, module, path): """Short cut for setting module path in djConfig.""" if 'modulePaths' not in self.dj_config: self.dj_config['modulePaths'] = {} self.dj_config['modulePaths'][module] = path def append_module(self, module): """Add a required module.""" if module not in self._modules: self._modules.append(module) def append_stylesheet(self, stylesheet): """Add a stylesheet.""" if stylesheet not in self._stylesheets: self._stylesheets.append(stylesheet) def append_aol(self, aol): """Add an addOnLoad function.""" self._aols.append(aol) def prepend_aol(self, aol): self._aols.insert(0, aol) def dojo_form(self, form, form_function=None): """ Generates Javascript to instrument a Dijit form. Adds an addOnLoad handler that instantiates a Dijit form when the page loads. Any fields that have been setup with dojo_field will also be instantiated as Dijits. """ dijits = [] for bound_field in form: field = bound_field.field dojo_type = getattr(field, '_dojo_type', None) if dojo_type is not None: # This is a dojo enabled widget params = getattr(field, '_dojo_params', {}) params['dojoType'] = dojo_type self.append_module(dojo_type) dijits.append({'name': bound_field.name, 'params': params}) json_dijits = json.dumps(dijits) self.append_module('dijit.form.Form') if getattr(form, 'id', None) is None: # Form must have a unique id for # JS form instrumentation to work # correctly. form.id = str(uuid.uuid1()) if form_function is None: # Form function wasn't passed, # use form_function member var instead form_function = self.form_function if form_function is None: # Generate JS self.append_module('dojo.parser') self.append_aol(self._build_form_js(form.id, json_dijits)) else: # Call existing JS function self.append_module(form_function[0]) function_name = '.'.join(form_function) function_call = "function() {%s('%s', %s);}" % (function_name, form.id, json_dijits) self.append_aol(function_call) def _build_form_js(self, form_id, json_dijits): """Builds a JS function string used to dijitize a form.""" return """ function () { var dijitForm = dojo.byId('%(form_id)s'); if (dijitForm != null) { var elements = []; var dijits = %(json_dijits)s; dojo.forEach(dijits, function(dijit) { var n = dijitForm.elements[dijit.name]; if (n != null) { dojo.attr(n, dijit.params); elements.push(n); } }); dojo.attr(dijitForm, {dojoType: 'dijit.form.Form'}); elements.push(dijitForm); dojo.parser.instantiate(elements); } } """ % {'form_id': form_id, 'json_dijits': json_dijits}