├── django_react_components ├── __init__.py ├── templatetags │ ├── __init__.py │ └── django_react_components.py ├── apps.py └── checks.py ├── MANIFEST.in ├── requirements.in ├── .gitignore ├── .github └── dependabot.yml ├── requirements.txt ├── LICENSE ├── setup.py └── README.md /django_react_components/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_react_components/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | Django==4.2 2 | django-webpack-loader==3.1.1 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | dist 4 | build 5 | .python-version 6 | .idea/ 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: django 11 | versions: 12 | - 3.1.5 13 | - 3.1.6 14 | -------------------------------------------------------------------------------- /django_react_components/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DjangoReactComponentsConfig(AppConfig): 5 | name = 'django_react_components' 6 | 7 | def ready(self): 8 | # Add System checks 9 | from django_react_components.checks import dependency_checks 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # pip-compile 6 | # 7 | asgiref==3.8.1 8 | # via django 9 | django==4.2 10 | # via -r requirements.in 11 | django-webpack-loader==3.1.1 12 | # via -r requirements.in 13 | sqlparse==0.4.2 14 | # via django 15 | -------------------------------------------------------------------------------- /django_react_components/checks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django System Checks 3 | """ 4 | from django.conf import settings 5 | from django.core.checks import Error, Tags, register 6 | 7 | 8 | @register(Tags.compatibility) 9 | def dependency_checks(app_configs, **kwargs): 10 | """ 11 | System check for required dependencies. 12 | """ 13 | errors = [] 14 | try: 15 | import webpack_loader 16 | if 'webpack_loader' not in settings.INSTALLED_APPS: 17 | errors.append( 18 | Error( 19 | '`django-react-components` requires `django-webpack-loader` to be installed.', 20 | hint='Add `webpack_loader` to `settings.INSTALLED_APPS`.', 21 | id='drl.E002', 22 | ) 23 | ) 24 | except ImportError: 25 | errors.append( 26 | Error( 27 | '`django-react-components` requires `django-webpack-loader` to be installed.', 28 | hint='Please install `django-webpack-loader`.', 29 | id='drl.E001', 30 | ) 31 | ) 32 | return errors 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Zagaran, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import find_packages, setup 3 | 4 | 5 | with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme: 6 | README = readme.read() 7 | 8 | # Allow setup.py to be run from any path 9 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 10 | 11 | setup( 12 | author='Zagaran, Inc.', 13 | author_email='info@zagaran.com', 14 | classifiers=[ 15 | 'Development Status :: 3 - Alpha', 16 | 'Environment :: Web Environment', 17 | 'Framework :: Django', 18 | 'Framework :: Django :: 1.11', 19 | 'Framework :: Django :: 2.0', 20 | 'Framework :: Django :: 2.1', 21 | 'Framework :: Django :: 2.2', 22 | 'Intended Audience :: Developers', 23 | 'License :: OSI Approved :: MIT License', 24 | 'Operating System :: OS Independent', 25 | 'Programming Language :: Python', 26 | 'Programming Language :: Python :: 3', 27 | 'Programming Language :: Python :: 3.4', 28 | 'Programming Language :: Python :: 3.5', 29 | 'Programming Language :: Python :: 3.6', 30 | 'Programming Language :: Python :: 3.7', 31 | 'Topic :: Internet :: WWW/HTTP', 32 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 33 | 'Topic :: Software Development', 34 | 'Topic :: Software Development :: User Interfaces', 35 | ], 36 | description='A collection of tools that assist with loading and rendering React components', 37 | license='MIT', 38 | long_description=README, 39 | long_description_content_type="text/markdown", 40 | name='django-react-components', 41 | packages=find_packages(), 42 | url='https://github.com/zagaran/django-react-components', 43 | version='1.0.0', 44 | ) 45 | -------------------------------------------------------------------------------- /django_react_components/templatetags/django_react_components.py: -------------------------------------------------------------------------------- 1 | """ 2 | Template tags for django-react-components 3 | """ 4 | import uuid 5 | 6 | from django import template 7 | from django.conf import settings 8 | from django.template import TemplateSyntaxError 9 | from django.template.base import token_kwargs 10 | from django.utils.html import format_html, json_script 11 | from django.utils.module_loading import import_string 12 | 13 | register = template.Library() 14 | 15 | encoder_class_import_string = getattr(settings, "DJANGO_REACT_JSON_ENCODER", "django.core.serializers.json.DjangoJSONEncoder") 16 | if not isinstance(encoder_class_import_string, str): 17 | raise ImportError("DJANGO_REACT_JSON_ENCODER must be set as an import string.") 18 | else: 19 | encoder_class = import_string(encoder_class_import_string) 20 | 21 | 22 | @register.simple_tag 23 | def react_component(component_name, component_id=None, props=None, **kwargs): 24 | """ 25 | Render a React component with kwargs as attributes. This current requires that all kwargs 26 | being passed in be JSON-serializable. 27 | """ 28 | if component_id is None: 29 | component_id = str(uuid.uuid4()) 30 | if props is None: 31 | props = {} 32 | props.update(id=component_id) 33 | props.update(kwargs) 34 | props_id = component_id+'_props' 35 | 36 | react_component_html = """ 37 |
38 | {props} 39 | 43 | """ 44 | 45 | return format_html( 46 | react_component_html, 47 | props_id=props_id, 48 | props=json_script(props, props_id, encoder=encoder_class), 49 | html_id=component_id, 50 | component_name=component_name, 51 | ) 52 | 53 | 54 | class ReactBlockNode(template.Node): 55 | def __init__(self, component, nodelist, component_id=None, props=None, **kwargs): 56 | if component_id is None: 57 | component_id = str(uuid.uuid4()) 58 | self.component = component 59 | self.html_id = component_id 60 | self.props = props 61 | self.kwargs = kwargs 62 | self.nodelist = nodelist 63 | 64 | def render(self, context): 65 | component = self.component.resolve(context) 66 | html_id = self.html_id.resolve(context) 67 | resolved_props = {key: value.resolve(context) for key, value in self.kwargs.items()} 68 | if self.props is not None: 69 | resolved_props.update(self.props.resolve(context)) 70 | resolved_props['id'] = html_id 71 | resolved_props['children'] = self.nodelist.render(context) 72 | props_id = html_id + '_props' 73 | 74 | react_component_html = """ 75 |
76 | {props} 77 | 81 | """ 82 | return format_html( 83 | react_component_html, 84 | props=json_script(resolved_props, props_id, encoder=encoder_class), 85 | props_id=props_id, 86 | html_id=html_id, 87 | component=component 88 | ) 89 | 90 | 91 | @register.tag(name='react') 92 | def do_react_block(parser, token): 93 | """ 94 | Render a React component with kwargs as attributes. This currently requires that all kwargs 95 | being passed in be JSON-serializable. 96 | """ 97 | bits = token.split_contents() 98 | if len(bits) < 2: 99 | raise TemplateSyntaxError("'%s' takes at least one argument, a React component name." % bits[0]) 100 | 101 | component = parser.compile_filter(bits[1]) 102 | remaining_bits = bits[2:] 103 | kwargs = token_kwargs(remaining_bits, parser) 104 | if remaining_bits: 105 | raise TemplateSyntaxError("%r received an invalid token: %r" % 106 | (bits[0], remaining_bits[0])) 107 | 108 | nodelist = parser.parse(('endreact',)) 109 | parser.delete_first_token() 110 | return ReactBlockNode(component, nodelist, **kwargs) 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django React Components 2 | 3 | `django-react-components` and it's sibling package `django-react-loader` facilitate using individual react components 4 | in a django template with a simple template tag. This also webpack, and the paired packages `webpack-bundle-tracker` 5 | and `django-webpack-loader` to compile the react components and make them available within django. 6 | 7 | ## Installation 8 | 9 | For normal usage, first install `django-react-components` using pip: 10 | ```bash 11 | $ pip install django-react-components 12 | ``` 13 | Add 'django_react_components' and 'webpack_loader' modules to your `INSTALLED_APPS` in `settings.py`: 14 | 15 | ```python 16 | INSTALLED_APPS = ( 17 | ..., 18 | 'django_react_components', 19 | 'webpack_loader', 20 | ) 21 | ``` 22 | 23 | ## Additional Requirements 24 | 25 | Install Webpack 5 [Installation Guide](https://webpack.js.org/guides/installation/) 26 | The command may resemble: 27 | 28 | ```bash 29 | npm install --save-dev webpack 30 | ``` 31 | 32 | Install `django-react-loader` with your preferred package manager: 33 | 34 | ```bash 35 | $ npm install --save-dev django-react-loader 36 | ``` 37 | _or_ 38 | ```bash 39 | $ yarn add django-react-loader --dev 40 | ``` 41 | 42 | ## Usage 43 | 44 | ### Configuration 45 | 46 | Configure Webpack: [Webpack Configuration Guide](https://webpack.js.org/configuration/). The rest of this guide assumes a webpack config file (probably called `webpack.config.js`) 47 | 48 | #### Setting up `django-webpack-loader` and `webpack-bundle-tracker` 49 | 50 | Follow instructions in the [Django Webpack Loader Docs](https://github.com/django-webpack/django-webpack-loader) 51 | 52 | #### Setting up `django-react-loader` 53 | 54 | Modify the webpack config file so that `django-react-loader` loads the react files: 55 | * Import the django-react-loader 56 | * Specify ENTRIES - a mapping of the names of your components to the source code file 57 | * Add `django-react-loader` to loaders 58 | 59 | Example configuration outline: 60 | 61 | ```js 62 | //wepback.config.js 63 | const DjangoReactLoader = require('django-react-loader'); 64 | ..., 65 | const ENTRIES = { 66 | ..., 67 | nameOfComponent: componentImportPath 68 | 69 | } 70 | ..., 71 | 72 | module.exports = { 73 | ..., 74 | module: { 75 | rules: [ 76 | ..., 77 | { 78 | test: /\.js$/, 79 | exclude: /node_modules/, 80 | options: { 81 | entries: ENTRIES 82 | }, 83 | loader: DjangoReactLoader 84 | } 85 | ..., 86 | ``` 87 | 88 | ### Compiling React Components 89 | 90 | Compile your react components with `webpack`. 91 | 92 | Command likely to resemble ```webpack build``` 93 | 94 | 95 | ### Rendering React Components 96 | 97 | In your templates, you can render React components by using the `{% react_component %}` or the `{% react %}`template tag. To do so: 98 | 99 | 1. Load the template tag and the `render_bundle` tag from `django_webpack_loader`: 100 | ```python 101 | {% load django_react_components %} 102 | {% load render_bundle from webpack_loader %} 103 | 104 | ``` 105 | 106 | 2. Use `render_bundle` to pull in the appropriate javascript 107 | ``` 108 | 109 | {% render_bundle 'runtime' %} 110 | {% render_bundle 'App' %} 111 | 112 | ``` 113 | 114 | 3a. Use the `react_component` tag to render the component with keyword arguments as props 115 | ``` 116 | 117 | {% react_component 'App' component_id='app' prop1=prop1 prop2=prop2 %} 118 | 119 | ``` 120 | 121 | 3b. Use the `react`/`endreact` tags to render the component with rendered content inside. This will be passed as raw HTML to the component as the `children` prop. 122 | ``` 123 | 124 | {% react 'App' id='app' %} 125 |

Hello World

126 |

{{ content }}

127 | Link 128 | {% endreact 'App' id='app' %} 129 | 130 | ``` 131 | 132 | ### Custom Props Encoding 133 | 134 | `django_react_components` uses JSON to encode props into the React components. You can specify a custom JSON encoder 135 | class with the `DJANGO_REACT_JSON_ENCODER` settings in your settings file, otherwise the default DjangoJSONEncoder is used. 136 | The encoder will be passed to `json_script()` 137 | 138 | ## Requirements 139 | 140 | Python 3.11, Django 4.2 141 | --------------------------------------------------------------------------------