Django JSON form field on steroids using react-json-schema-form
Project description
django-reactive integrates react-jsonschema-form (RJSF) in Django projects.
Motivation
JSON types is a cool feature of Postgres that allows combining both relational and non-relational approaches to storing data. In most cases it leads to a simpler database design.
The JSONField in Django provides a nice way of using json and jsonb Postgres types in the model classes. ORM can even allow you to perform queries against the data stored inside the JSON structure. Moreover, it is possible to use GIN indexes with jsonb types in Postgres, what makes it a useful tool in the application design and opens a wide range of possibilities such as polymorphic behaviour, storing complex hierarchies and lists of related entities, etc.
However, the main limitation of JSONField in Django is a lack of good support of UI for JSON structures as defining JSON objects inside the textarea inputs is not practical for most use cases. django-reactive tries to address this problem by offering an integration between JSONField and the awesome react-jsonschema-form (RJSF) JavaScript library.
django-reactive also uses Python jsonschema <https://github.com/Julian/jsonschema> library for backend validation. Such integration can significantly reduce the amount of work you need to contribute into building custom forms for JSONField types.
In most cases it only requires a JSON schema configuration for such field and optionally a UI schema to modify some representation parameters.
A basic example of this is demonstrated below:
from django.db import models
from django_reactive.fields import ReactJSONSchemaField
class Registration(models.Model):
basic = ReactJSONSchemaField(
help_text="Registration form",
schema={
"title": "Register now!",
"description": "Fill out the form to register.",
"type": "object",
"required": [
"firstName",
"lastName"
],
"properties": {
"firstName": {
"type": "string",
"title": "First name"
},
"lastName": {
"type": "string",
"title": "Last name"
},
"age": {
"type": "integer",
"title": "Age"
},
"bio": {
"type": "string",
"title": "Bio"
},
"password": {
"type": "string",
"title": "Password",
"minLength": 3
},
"telephone": {
"type": "string",
"title": "Telephone",
"minLength": 10
}
}
},
ui_schema={
"firstName": {
"ui:autofocus": True,
"ui:emptyValue": ""
},
"age": {
"ui:widget": "updown",
"ui:title": "Age of person",
"ui:description": "(earthian year)"
},
"bio": {
"ui:widget": "textarea"
},
"password": {
"ui:widget": "password",
"ui:help": "Hint: Make it strong!"
},
"date": {
"ui:widget": "alt-datetime"
},
"telephone": {
"ui:options": {
"inputType": "tel"
}
}
},
)
It will generate a form like this:
Quick start
Install django-reactive:
pip install django-reactive
Add it to your INSTALLED_APPS:
INSTALLED_APPS = (
...
'django_reactive',
...
)
Running the example
Build the docker image for the Django application in example/:
Run docker-compose up -d
This will automatically create the database, run migrations, import the default superuser, and run the Django development server on http://127.0.0.1:8000.
Django admin example
Open http://127.0.0.1:8000/admin/ and login with username admin and password test.
Go to the “Test models” admin section to see the example forms.
Normal Django view example
Open http://127.0.0.1:8000/create/ to create a basic form example.
You will be redirected to the detail view of the created object after the form saves.
Usage outside of Django admin
To use outside of the Django admin, the following are required in the template:
A call to the form media property using {{ form.media }}
An HTML submit input with name=”_save”.
<!DOCTYPE html>
<html>
<head>
<title>Homepage</title>
</head>
<body>
{{ form.media }}
<form method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Save" name="_save">
</form>
</body>
</html>
Optional configuration
Schema fields accept the following parameters for additional configuration:
extra_css: Include additional static CSS files available in the widget.
extra_js: Include additional static JavaScript files available in the widget.
on_render: A python method to make dynamic schema modifications at render-time.
Extra CSS and JSS files should be accessible using Django’s staticfiles configurations and passed as a list of strings.
Render methods require both schema and ui_schema as arguments to allow dynamic schema modification when rendering the widget. An optional instance keyword argument may also be used for referencing an object instance (must be set on the widget in the form). This method does not return anything.
Example usage
The example below demonstrates a use-case in which the options available for a particular field may be dynamic and unavailable in the initial schema definition. These would be populated at render-time and made available in the form UI.
def set_task_types(schema, ui_schema):
from todos.models import TaskType
task_types = list(TaskType.objects.all().values_list("name", flat=True))
schema["definitions"]["Task"]["properties"]["task_type"]["enum"] = task_types
ui_schema["task_lists"]["items"]["tasks"]["items"]["task_type"][
"ui:help"
] = f"Select 1 of {len(task_types)} task types"
class Todo(models.Model):
"""
A collection of task lists for a todo.
"""
name = models.CharField(max_length=255)
task_lists = ReactJSONSchemaField(
help_text="Task lists",
schema=TODO_SCHEMA,
ui_schema=TODO_UI_SCHEMA,
on_render=set_task_types,
extra_css=["css/extra.css"],
extra_js=["js/extra.js"],
)
Schema model form class
The form class ReactJSONSchemaModelForm (subclassed from Django’s ModelForm) can be used to provide the model form’s instance object to the schema field widgets:
from django_reactive.forms import ReactJSONSchemaModelForm
class MyModelForm(ReactJSONSchemaModelForm):
...
This allows the on_render method set for a schema field to reference the instance like this:
def update_the_schema(schema, ui_schema, instance=None):
if instance and instance.some_condition:
ui_schema["my_schema_prop"]["ui:help"] = "Some extra help text"
Features
React, RJSF and other JS assets are bundled with the package.
Integration with default Django admin theme.
Backend and frontend validation.
Configurable static media assets
Dynamic schema mutation in widget renders
Limitations
Additional properties ( a feature of RJSF) is not supported.
To implement this behaviour you can define an array schema with one property serving as a key of the object and do transformation in your JSON class. An example will be provided later.
Future development
Display description as tooltips
Polish styles and HTML generated by RJSF
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file django-reactive-0.0.11.tar.gz
.
File metadata
- Download URL: django-reactive-0.0.11.tar.gz
- Upload date:
- Size: 358.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/33.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.63.0 importlib-metadata/4.11.2 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.9.10
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | f0dcb42fef4f5463c14e53be41fcaf2930157c7b53c0e90542542de9f41566e1 |
|
MD5 | 72c4412fdc90ada780e4cfe43e7e0076 |
|
BLAKE2b-256 | 440ef16da3d23af90dbe9ada485ed2b7353fed4a07fd3624419cd12101849e4f |
File details
Details for the file django_reactive-0.0.11-py3-none-any.whl
.
File metadata
- Download URL: django_reactive-0.0.11-py3-none-any.whl
- Upload date:
- Size: 360.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/33.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.63.0 importlib-metadata/4.11.2 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.9.10
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | bbe4e723f1b78c4c69ed0c4543e3d14e2383d3c37287819161870869a758dd31 |
|
MD5 | afc37b3912507795602ca9e76942042c |
|
BLAKE2b-256 | 5bad336dfa7418ffc84abad7857b25253b26ebed7a0306d5fd3def27fc4de8bb |