Skip to content

Commit 0a96069

Browse files
committed
Initial commit of the django_react app.
1 parent 5a9bfae commit 0a96069

12 files changed

Lines changed: 501 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,5 @@ docs/_build/
5252

5353
# PyBuilder
5454
target/
55+
56+
node_modules

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Changelog
2+
=========
3+
4+
### 0.1.0 (22/12/2014)
5+
6+
- Initial release

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Django React
2+
============
3+
4+
Provides an interface for Django to render and bundle React components.

django_react/__init__.py

Whitespace-only changes.

django_react/bundle.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
var fs = require('fs');
2+
var path = require('path');
3+
var argv = require('yargs').argv;
4+
var webpack = require('webpack');
5+
6+
var entry = argv.entry;
7+
if (!entry) {
8+
throw new Error('No entry point specified for the bundle, ex: `--entry=/path/to/some/file.js`');
9+
}
10+
11+
if (!fs.existsSync(entry)) {
12+
throw new Error('Cannot find entry file "' + entry + '"');
13+
}
14+
15+
var output = argv.output;
16+
if (!output) {
17+
throw new Error('No output path specified for the bundle, ex: `--output=/path/to/some/file.js`');
18+
}
19+
20+
var library = argv.library;
21+
if (!library) {
22+
throw new Error('No library name specified for the bundle, ex: `--library=someVariable`');
23+
}
24+
25+
26+
webpack({
27+
entry: entry,
28+
output: {
29+
filename: output,
30+
library: library
31+
},
32+
// TODO: make this extensible
33+
externals: {
34+
// Rather than bundling React, we use the browser's global
35+
react: 'window.React',
36+
'react/addons': 'window.React'
37+
},
38+
module: {
39+
loaders: [
40+
// Pass *.jsx files through the jsx-loader transform
41+
{ test: /\.jsx$/, loader: 'jsx' }
42+
]
43+
},
44+
resolveLoader: {
45+
// Instruct webpack to use our node_modules for resolving loaders
46+
root: [path.join(__dirname, 'node_modules')]
47+
},
48+
// TODO: make this configurable
49+
devtool: 'eval-source-map'
50+
// TODO: make this configurable, and try to see if we can resolve the file where the error is triggered
51+
// bail: true
52+
}, function(err) {
53+
if (err) {
54+
throw new Error(err);
55+
}
56+
});

django_react/exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class ReactComponentSourceFileNotFound(Exception):
2+
pass
3+
4+
5+
class ReactComponentRenderToStringException(Exception):
6+
pass
7+
8+
9+
class ReactComponentBundleException(Exception):
10+
pass

django_react/models.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import os
2+
import hashlib
3+
import json
4+
from django.contrib.staticfiles import finders
5+
from django.utils.safestring import mark_safe
6+
from .exceptions import ReactComponentSourceFileNotFound
7+
from .settings import STATIC_URL, STATIC_ROOT
8+
from .utils import render, bundle
9+
10+
11+
class ReactComponent(object):
12+
source = None
13+
# TODO
14+
# exported_as = None
15+
props = None
16+
serialised_props = None
17+
path_to_bundled_source = None
18+
19+
def __init__(self, **kwargs):
20+
self.props = kwargs
21+
22+
def render(self):
23+
return self.render_container(
24+
content=render(self)
25+
)
26+
27+
def render_static(self):
28+
return self.render_container(
29+
content=render(self, to_static_markup=True)
30+
)
31+
32+
def render_js(self):
33+
rendered_component = '\n'.join((
34+
self.render_props(),
35+
self.render_source(),
36+
self.render_init()
37+
))
38+
return mark_safe(rendered_component)
39+
40+
def render_container(self, content=None):
41+
if content is None:
42+
content = ''
43+
rendered_mount_element = '<div id="{container_id}" class="{container_class_name}">{content}</div>'.format(
44+
container_id=self.get_container_id(),
45+
container_class_name=self.get_container_class_name(),
46+
content=content,
47+
)
48+
return mark_safe(rendered_mount_element)
49+
50+
def render_props(self):
51+
rendered_props = '<script>{props_variable} = {serialised_props};</script>'.format(
52+
props_variable=self.get_props_variable(),
53+
serialised_props=self.get_serialised_props(),
54+
)
55+
return mark_safe(rendered_props)
56+
57+
def render_source(self):
58+
rendered_bundle = '<script src="{url_to_mount_bundle}"></script>'.format(
59+
url_to_mount_bundle=self.get_url_to_source()
60+
)
61+
return mark_safe(rendered_bundle)
62+
63+
def render_init(self):
64+
props_check = ''
65+
if self.has_props():
66+
props_check = '''
67+
if (!window.{props_variable}) {{
68+
throw new Error('Cannot find props `{props_variable}` for component `{exported_variable}`');
69+
}}
70+
'''.format(
71+
props_variable=self.get_props_variable(),
72+
exported_variable=self.get_component_variable(),
73+
)
74+
rendered_mount_script = '''
75+
<script>
76+
if (!window.{exported_variable}) {{
77+
throw new Error('Cannot find component `{exported_variable}`');
78+
}}
79+
{props_check}
80+
(function(React, component, props, container_id) {{
81+
var react_element = React.createElement(component, props);
82+
var mount_container = document.getElementById(container_id);
83+
if (!mount_container) {{
84+
throw new Error('Cannot find the container element `#' + container_id + '` for component `{exported_variable}`');
85+
}}
86+
React.render(react_element, mount_container);
87+
}})({react_variable}, {exported_variable}, window.{props_variable}, '{container_id}');
88+
</script>
89+
'''.format(
90+
react_variable=self.get_react_variable(),
91+
exported_variable=self.get_component_variable(),
92+
props_variable=self.get_props_variable(),
93+
container_id=self.get_container_id(),
94+
props_check=props_check,
95+
)
96+
return mark_safe(rendered_mount_script)
97+
98+
def get_source(self):
99+
return self.source
100+
101+
def get_path_to_source(self):
102+
source = self.get_source()
103+
path_to_source = finders.find(source)
104+
if not os.path.exists(path_to_source):
105+
raise ReactComponentSourceFileNotFound(path_to_source)
106+
return path_to_source
107+
108+
def get_props(self):
109+
return self.props
110+
111+
def has_props(self):
112+
if self.get_props():
113+
return True
114+
return False
115+
116+
# TODO
117+
# def get_exported_as(self):
118+
# return self.exported_as
119+
120+
def get_component_name(self):
121+
return self.__class__.__name__
122+
123+
def get_component_variable(self):
124+
return self.get_component_name()
125+
126+
def get_react_variable(self):
127+
return 'React'
128+
129+
def get_component_id(self):
130+
return unicode(id(self))
131+
132+
def get_container_id(self):
133+
return 'reactComponentContainer-{component_id}'.format(
134+
component_id=self.get_component_id()
135+
)
136+
137+
def get_container_class_name(self):
138+
return 'reactComponentContainer reactComponentContainer--{component_name}'.format(
139+
component_name=self.get_component_name()
140+
)
141+
142+
def get_serialised_props(self):
143+
if not self.serialised_props:
144+
self.serialised_props = json.dumps(self.get_props())
145+
return self.serialised_props
146+
147+
def get_serialised_props_hash(self):
148+
serialised_props = self.get_serialised_props()
149+
md5 = hashlib.md5()
150+
md5.update(serialised_props)
151+
return md5.hexdigest()
152+
153+
def get_props_variable(self):
154+
return '__propsFor{component_name}_{serialised_props_hash}__'.format(
155+
component_name=self.get_component_name(),
156+
serialised_props_hash=self.get_serialised_props_hash(),
157+
)
158+
159+
def get_path_to_bundled_source(self):
160+
if not self.path_to_bundled_source:
161+
self.path_to_bundled_source = bundle(self)
162+
return self.path_to_bundled_source
163+
164+
def get_url_to_source(self):
165+
_, rel_path_to_bundled_source = self.get_path_to_bundled_source().split(STATIC_ROOT)
166+
if rel_path_to_bundled_source[0] in ['/', '\\']:
167+
rel_path_to_bundled_source = rel_path_to_bundled_source[1:]
168+
return os.path.join(STATIC_URL, rel_path_to_bundled_source)

django_react/package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "django_react",
3+
"version": "0.0.1",
4+
"description": "",
5+
"author": "Mark Finger <markfinger@gmail.com>",
6+
"license": "MIT",
7+
"dependencies": {
8+
"jsx-loader": "^0.12.2",
9+
"node-jsx": "^0.12.4",
10+
"react": "^0.12.1",
11+
"webpack": "^1.4.13",
12+
"yargs": "^1.3.3"
13+
}
14+
}

django_react/render.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
var fs = require('fs');
2+
var path = require('path');
3+
var argv = require('yargs').argv;
4+
var React = require('react');
5+
var nodeJSX = require('node-jsx');
6+
7+
var pathToComponent = argv.pathToComponent;
8+
if (!pathToComponent) {
9+
throw new Error('No path to the component specified, ex: `--path-to-component /path/to/some/component.js`');
10+
}
11+
12+
if (!fs.existsSync(pathToComponent)) {
13+
throw new Error('Cannot find file "' + pathToComponent + '"')
14+
}
15+
16+
// Install support for requiring JSX files
17+
nodeJSX.install();
18+
19+
var component = require(pathToComponent);
20+
21+
var RENDER_TO_STATIC = 'static';
22+
var RENDER_TO_STRING = 'string';
23+
24+
var renderTo = argv.renderTo;
25+
if (!renderTo) {
26+
throw new Error('No render to option provided, ex: `--render-to ' + RENDER_TO_STATIC + '` or `--render-to ' + RENDER_TO_STRING + '`');
27+
}
28+
if (renderTo !== RENDER_TO_STATIC && renderTo !== RENDER_TO_STRING) {
29+
throw new Error('Unknown render to option "' + renderTo + '", only "--render-to ' + RENDER_TO_STATIC + '" and "--render-to ' + RENDER_TO_STRING + '" are accepted');
30+
}
31+
32+
var serialisedProps = argv.serialisedProps;
33+
if (!serialisedProps) {
34+
throw new Error('No path to props file provided');
35+
}
36+
37+
serialisedProps = fs.readFileSync(serialisedProps);
38+
39+
var props = JSON.parse(serialisedProps);
40+
41+
var element = React.createElement(component, props);
42+
43+
var output;
44+
if (renderTo === RENDER_TO_STATIC) {
45+
output = React.renderToStaticMarkup(element);
46+
} else if (renderTo === RENDER_TO_STRING) {
47+
output = React.renderToString(element);
48+
}
49+
50+
// Push the output to stdout
51+
console.log(output);

django_react/settings.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import os
2+
from django.conf import settings
3+
4+
setting_overrides = getattr(settings, 'DJANGO_REACT', {})
5+
6+
ENGINE = setting_overrides.get(
7+
'ENGINE',
8+
'node'
9+
)
10+
11+
RENDERER = setting_overrides.get(
12+
'RENDERER',
13+
os.path.abspath(os.path.join(__file__, '../render.js'))
14+
)
15+
16+
BUNDLER = setting_overrides.get(
17+
'BUNDLER',
18+
os.path.abspath(os.path.join(__file__, '../bundle.js'))
19+
)
20+
21+
STATIC_ROOT = setting_overrides.get(
22+
'STATIC_ROOT',
23+
settings.STATIC_ROOT,
24+
)
25+
26+
STATIC_URL = setting_overrides.get(
27+
'STATIC_URL',
28+
settings.STATIC_URL,
29+
)

0 commit comments

Comments
 (0)