Created
March 17, 2015 21:46
-
-
Save Naddiseo/39f2154b906a709ac26c to your computer and use it in GitHub Desktop.
Django url serialization for use in javascript. Requires `simple-fmt`, `lodash`, and ES6. (Note, will not work as is)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import last from 'lodash/array/last'; | |
import isObject from 'lodash/lang/isObject'; | |
export default function pyargs(...args) { | |
let kwargs = last(args); | |
if (!isObject(kwargs)) { | |
kwargs = {}; | |
} | |
else { | |
args.pop(); | |
} | |
return [args, kwargs]; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
from django.core.urlresolvers import RegexURLPattern, RegexURLResolver | |
from django.utils.regex_helper import normalize | |
from project.views.base import AjaxView | |
py2js_rx = re.compile('\(\?P\<\w+>(?P<innerrx>[^\)]+)\)') | |
def make_js_rx(s): | |
return py2js_rx.sub('(\g<innerrx>)', s) | |
class GetURLsView(AjaxView): | |
def _make_resolver(self, p = None, children = None, name = '', namespace_so_far = '') -> dict: | |
rx = make_js_rx(p.regex.pattern) if p is not None else '^/$' | |
ret = { | |
'type' : 'Resolver', | |
'name' : name, | |
'regex' : rx, | |
'normalized' : normalize(rx), | |
'namespace' : (p.namespace or '') if p is not None else '', | |
'children' : [] | |
} | |
namespace_so_far = ':'.join(filter(None, [namespace_so_far, ret['namespace']])) | |
c = self._traverse_resolver(p, namespace_so_far) if children is None else children | |
if c: | |
ret['children'] = c | |
return ret | |
def _traverse_resolver(self, p: RegexURLResolver, namespace_so_far : str) -> dict: | |
assert isinstance(p, RegexURLResolver) | |
assert hasattr(p, 'url_patterns') | |
return self._traverse(p.url_patterns, p.namespace, namespace_so_far) | |
def _traverse(self, patterns, namespace = '', namespace_so_far = ''): | |
ret = [] | |
for p in patterns: | |
if isinstance(p, RegexURLResolver): | |
resolver = self._make_resolver(p, None, '', namespace_so_far) | |
if not p.namespace: | |
# Promote a RegexResolver | |
normalized = normalize(p.regex.pattern) | |
for child in resolver['children']: | |
child['normalized'] = normalized + child['normalized'] | |
child['regex'] = resolver['regex'] + child['regex'][1:] | |
#assert name not in ret | |
ret.append(child) | |
else: | |
ret.append(resolver) | |
elif isinstance(p, RegexURLPattern): | |
view_name = ':'.join(filter(None, [namespace_so_far, p.name])) | |
if not self.security.can_see(view_name): | |
continue | |
ret.append({ | |
'type' : 'Pattern', | |
'name' : p.name if p.name else namespace, | |
'normalized' : normalize(p.regex.pattern), | |
'regex' : py2js_rx.sub(r'(\g<innerrx>)', p.regex.pattern) | |
}) | |
return ret | |
def get(self, *args, **kwargs): | |
from project import urls | |
self.context = self._make_resolver(None, self._traverse(urls.urlpatterns)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import assert from 'assert'; // TODO: remove | |
import has from 'lodash/object/has'; | |
import pyargs from './pyargs'; | |
class NoReverseMatch extends Error { | |
constructor(path, args, kwargs, msg) { | |
var m = "Could not find url `{0}` with args `{1}` and kwargs `{2}`".format( | |
path, JSON.stringify(args), JSON.stringify(kwargs) | |
); | |
if (msg) { | |
m += ": {0}".format(msg); | |
} | |
this.message = m; | |
super(m); | |
} | |
} | |
function reversePatterns(argNames, args, kwargs, path) { | |
var ret = ''; | |
argNames.forEach(function([patString, names]) { | |
var replacements = {}; | |
var lastIdx = -1; | |
if (args.length && names.length) { | |
assert.ok(args.length >= names.length, "Not enough args for all named parameters"); | |
names.forEach(function(name) { | |
var value = args.shift(); | |
var replacement = '%({0})s'.format(name); | |
lastIdx = patString.indexOf(replacement, lastIdx); | |
if (lastIdx < 0) { | |
throw new NoReverseMatch(path, args, kwargs, "Could not find pattern for arg {0}".format(name)); | |
} | |
ret += "{0}{1}".format(patString.slice(0, lastIdx), value); | |
lastIdx += replacement.length; | |
}); | |
} | |
else if (Object.keys(kwargs).length && names.length) { | |
assert.ok(Object.keys(kwargs).length >= names.length, "Not enough kwargs for all named parameters"); | |
names.forEach(function(name) { | |
if (!has(kwargs, name)) { | |
throw new NoReverseMatch(path, args, kwargs, "expected kwarg `{0}`".format(name)); | |
} | |
var value = kwargs[name]; | |
var replacement = '%({0})s'.format(name); | |
lastIdx = patString.indexOf(replacement, lastIdx); | |
if (lastIdx < 0) { | |
throw new NoReverseMatch(path, args, kwargs, "Could not find pattern for arg {0}".format(name)); | |
} | |
ret += "{0}{1}".format(patString.slice(0, lastIdx), value); | |
lastIdx += replacement.length; | |
}); | |
} | |
else if (names.length) { | |
throw new NoReverseMatch(path, args, kwargs, "No args or kwargs specified but had {0} parameter(s) to fill".format(names.length)); | |
} | |
else { | |
lastIdx = 0; | |
} | |
ret += patString.slice(lastIdx); | |
}); | |
return ret; | |
} | |
class RegexURLPattern { | |
constructor(pattern, name, argNames) { | |
this.pattern = new RegExp(pattern); | |
this.name = name; | |
this.argNames = argNames; | |
} | |
/* | |
returns a constructed url given the args/kwargs | |
*/ | |
reverse(path, ...params) { | |
var ret = ''; | |
var [args, kwargs] = pyargs(...params); | |
assert.equal(path, this.name); | |
if (args.length && Object.keys(kwargs).length) { | |
throw new Error("Don't mix args and kwargs in call to reverse()"); | |
} | |
return reversePatterns(this.argNames, args, kwargs, path); | |
} | |
static FromJson(s) { | |
assert.equal(s.type, 'Pattern'); | |
return new RegexURLPattern(s.regex, s.name, s.normalized); | |
} | |
} | |
class RegexURLResolver { | |
constructor(pattern, name, argNames = [], ns = '') { | |
this.pattern = new RegExp(pattern); | |
this.name = name; | |
this.argNames = argNames; | |
this.ns = ns; | |
this.children = []; | |
this._nameToChild = {}; | |
this._nsToChild = {}; | |
} | |
/* | |
Returns the url as a string with the parameters substituted. | |
*/ | |
reverse(path, ...params) { | |
var namespaces = path.split(':'); | |
var viewName = namespaces.pop(); | |
var [args, kwargs] = pyargs(...params); | |
var reverser, ret = reversePatterns(this.argNames, args, kwargs, path); | |
params = args.concat([kwargs]); | |
if (namespaces.length) { | |
if (!this._nsToChild[namespaces[0]]) { | |
throw new NoReverseMatch(path, args, kwargs); | |
} | |
reverser = this._nsToChild[namespaces[0]]; | |
path = namespaces.slice(1).concat([viewName]).join(':'); | |
} | |
else if (viewName) { | |
if (!this._nameToChild[viewName]) { | |
throw new NoReverseMatch(path, args, kwargs); | |
} | |
reverser = this._nameToChild[viewName]; | |
} | |
else { | |
throw new NoReverseMatch(path, args, kwargs); | |
} | |
return '{0}{1}'.format(ret, reverser.reverse(path, ...params)); | |
} | |
addChild(child) { | |
this.children.push(child); | |
if (child instanceof RegexURLResolver) { | |
this._nsToChild[child.ns] = child; | |
} | |
else if (child instanceof RegexURLPattern && child.name.length) { | |
this._nameToChild[child.name] = child; | |
} | |
} | |
static FromJson(s) { | |
assert.equal(s.type, 'Resolver'); | |
var ret = new RegexURLResolver(s.regex, s.name, s.normalized, s.namespace); | |
s.children.forEach(function(child) { | |
switch (child.type) { | |
case 'Resolver': | |
ret.addChild(RegexURLResolver.FromJson(child)); | |
break; | |
case 'Pattern': | |
ret.addChild(RegexURLPattern.FromJson(child)); | |
break; | |
default: | |
throw new Error("Unknown url type: " + child.type); | |
} | |
}); | |
return ret; | |
} | |
} | |
export { RegexURLResolver }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Python side:
Usage on js side (with mithril):