├── .coveragerc ├── .github ├── FUNDING.yml └── workflows │ └── tests.yml ├── .gitignore ├── .readthedocs.yaml ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── django_jsonform ├── __init__.py ├── admin.py ├── apps.py ├── constants.py ├── exceptions.py ├── forms │ ├── __init__.py │ ├── compat.py │ └── fields.py ├── models │ ├── __init__.py │ ├── compat.py │ └── fields.py ├── static │ └── django_jsonform │ │ ├── img │ │ ├── arrow-icons.svg │ │ ├── collapsed-indicator-bg-dark.svg │ │ ├── collapsed-indicator-bg.svg │ │ ├── control-icons.svg │ │ ├── icon-addlink.svg │ │ ├── icon-changelink.svg │ │ └── icon-deletelink.svg │ │ ├── index.js │ │ ├── react-json-form.js │ │ ├── style.css │ │ └── vendor │ │ ├── react-dom.production.min.js │ │ ├── react-modal.min.js │ │ └── react.production.min.js ├── templates │ └── django_jsonform │ │ ├── attrs.html │ │ └── editor.html ├── templatetags │ ├── __init__.py │ └── django_jsonform.py ├── tests.py ├── urls.py ├── utils.py ├── validators.py ├── views.py └── widgets.py ├── docs ├── Makefile ├── _ext │ └── helpers.py ├── _static │ ├── autocomplete.gif │ ├── candy.css │ ├── favicon.ico │ ├── file-upload.gif │ ├── logo.png │ └── quickstart.gif ├── _templates │ └── layout.html ├── conf.py ├── examples.rst ├── fields-and-widgets.rst ├── guide │ ├── arrayfield.rst │ ├── autocomplete.rst │ ├── choices.rst │ ├── coordinates.rst │ ├── index.rst │ ├── inputs.rst │ ├── javascript.rst │ ├── translations.rst │ ├── upload.rst │ └── validation.rst ├── index.rst ├── installation.rst ├── make.bat ├── quickstart.rst ├── releases │ ├── index.rst │ ├── v1.0.0.rst │ ├── v2.0.0.rst │ ├── v2.1.0.rst │ ├── v2.10.0.rst │ ├── v2.10.1.rst │ ├── v2.11.0.rst │ ├── v2.11.1.rst │ ├── v2.12.0.rst │ ├── v2.13.0.rst │ ├── v2.14.0.rst │ ├── v2.15.0.rst │ ├── v2.16.0.rst │ ├── v2.16.1.rst │ ├── v2.17.0.rst │ ├── v2.17.1.rst │ ├── v2.17.2.rst │ ├── v2.17.3.rst │ ├── v2.17.4.rst │ ├── v2.18.0.rst │ ├── v2.19.0.rst │ ├── v2.19.1.rst │ ├── v2.2.0.rst │ ├── v2.2.1.rst │ ├── v2.20.0.rst │ ├── v2.20.1.rst │ ├── v2.20.2.rst │ ├── v2.21.0.rst │ ├── v2.21.1.rst │ ├── v2.21.2.rst │ ├── v2.21.3.rst │ ├── v2.21.4.rst │ ├── v2.21.5.rst │ ├── v2.22.0.rst │ ├── v2.23.0.rst │ ├── v2.23.1.rst │ ├── v2.23.2.rst │ ├── v2.3.0.rst │ ├── v2.4.0.rst │ ├── v2.5.0.rst │ ├── v2.6.0.rst │ ├── v2.7.0.rst │ ├── v2.8.0.rst │ ├── v2.8.1.rst │ └── v2.9.0.rst ├── requirements.txt ├── schema.rst ├── settings.rst ├── templatetags.rst ├── troubleshooting.rst └── utils.rst ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── __main__.py ├── django_settings.py ├── models ├── __init__.py └── test_fields.py ├── test_templatetags.py ├── test_utils.py ├── test_validators.py └── test_widgets.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = django_jsonform 4 | omit = *tests*,__main__ 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: bhch -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: django-jsonform tests 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] 15 | django-version: ["2.2", "3.2", "4.1", "4.2", "main"] 16 | exclude: 17 | - django-version: 2.2 18 | python-version: 3.10 19 | - django-version: 2.2 20 | python-version: 3.11 21 | - django-version: 3.2 22 | python-version: 3.11 23 | - django-version: 4.1 24 | python-version: 3.7 25 | - django-version: 4.2 26 | python-version: 3.7 27 | - django-version: "main" 28 | python-version: 3.7 29 | - django-version: "main" 30 | python-version: 3.8 31 | - django-version: "main" 32 | python-version: 3.9 33 | 34 | steps: 35 | - uses: actions/checkout@v3 36 | - name: Set up Python ${{ matrix.python-version }} 37 | uses: actions/setup-python@v4 38 | with: 39 | python-version: ${{ matrix.python-version }} 40 | - name: Upgrade pip 41 | run: python -m pip install --upgrade pip 42 | - name: Install Django ${{ matrix.django-version }} 43 | run: python -m pip install django~=${{ matrix.django-version }}.0 44 | if: matrix.django-version != 'main' 45 | - name: Install Django main 46 | run: python -m pip install https://github.com/django/django/archive/refs/heads/main.zip 47 | if: matrix.django-version == 'main' 48 | - name: Install library to run tests 49 | run: python -m pip install -e . 50 | - name: Verify versions 51 | run: | 52 | python --version 53 | python -c "import django ; print(django.VERSION)" 54 | - name: Run tests 55 | run: | 56 | python -m pip install coverage 57 | coverage run tests/__main__.py 58 | coverage report -m 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | 4 | env/ 5 | bin/ 6 | build/ 7 | _build/ 8 | develop-eggs/ 9 | dist/ 10 | eggs/ 11 | lib/ 12 | lib64/ 13 | parts/ 14 | sdist/ 15 | var/ 16 | *.egg-info/ 17 | *.egg 18 | .eggs 19 | .installed.cfg 20 | local/ 21 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.8" 7 | 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | python: 12 | install: 13 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, Bharat Chauhan 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 19 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE.txt 3 | recursive-include django_jsonform/static * 4 | recursive-include django_jsonform/templates * 5 | prune django_jsonform/static/django_jsonform/local -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | django-jsonform icon 3 |

4 | 5 |

6 | A user-friendly JSON editing form for django admin. 7 |

8 | 9 |

10 | 11 | 12 | 13 |

14 | 15 |

16 | Documentation • 17 | Playground • 18 | PyPI 19 |

20 | 21 | ## Features 22 | 23 | - [x] File uploads 24 | - [x] Postgres `ArrayField` 25 | - [x] Many inputs and field types 26 | - [x] UI matches with Django admin's 27 | - [x] Recursion (nesting with self references) 28 | - [x] Validation 29 | 30 | ## Screenshots 31 | 32 | Here's a screenshot of items being added to a shopping list (JSON array) dynamically: 33 | 34 | ![django-jsonform screenshot](https://raw.githubusercontent.com/bhch/django-jsonform/master/docs/_static/quickstart.gif) 35 | 36 | ## Install 37 | 38 | Install via pip: 39 | 40 | ```sh 41 | $ pip install django-jsonform 42 | ``` 43 | 44 | Edit your *settings.py* file: 45 | 46 | ```python 47 | # settings.py 48 | 49 | INSTALLED_APPS = [ 50 | # ... 51 | 'django_jsonform' 52 | ] 53 | ``` 54 | 55 | ## Upgrading notes 56 | 57 | When upgrading from an older version of this library, please ensure that your 58 | browser is loading the latest static JavaScript files that come with this library. 59 | 60 | - In the development environment, clear the browser cache. 61 | - In the production environment, you must run the `collectstatic` command to update 62 | the static files. 63 | 64 | ## Documentation 65 | 66 | Quickstart and usage docs can be found at [http://django-jsonform.rtfd.io](http://django-jsonform.rtfd.io). 67 | 68 | ## Contributing 69 | 70 | - The JavaScript code is written in React and it lives in another repo: https://github.com/bhch/react-json-form. 71 | The JS code lacks proper documentation or comments, so before contributing, maybe open an issue and I can help you out. 72 | - For everything else (related to Django or widget's css), contribute directly to this repo. 73 | 74 | ## License 75 | 76 | [BSD-3-Clause](LICENSE.txt) 77 | -------------------------------------------------------------------------------- /django_jsonform/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "2.23.2" -------------------------------------------------------------------------------- /django_jsonform/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /django_jsonform/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class JsonappConfig(AppConfig): 5 | name = 'django_jsonform' 6 | -------------------------------------------------------------------------------- /django_jsonform/constants.py: -------------------------------------------------------------------------------- 1 | """Constants for the frontend widget""" 2 | 3 | # Symbol for joining coordinates. 4 | # Earlier, a hyphen (-) was used. But that caused problems when 5 | # dict keys had hyphen in them. So, we're switching to a less 6 | # commonly used symbol. 7 | JOIN_SYMBOL = '§' 8 | -------------------------------------------------------------------------------- /django_jsonform/exceptions.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ValidationError 2 | 3 | 4 | class JSONSchemaValidationError(ValidationError): 5 | """Subclass of Django's ValidationError""" 6 | def __init__(self, message, code=None, params=None, error_map=None): 7 | super().__init__(message, code, params) 8 | self.error_map = error_map 9 | -------------------------------------------------------------------------------- /django_jsonform/forms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhch/django-jsonform/e485ef149902962a5b9ec83bcb1150ffbee64ef6/django_jsonform/forms/__init__.py -------------------------------------------------------------------------------- /django_jsonform/forms/compat.py: -------------------------------------------------------------------------------- 1 | """Compatibility fields for SQLite when Django < 3.1. 2 | 3 | Some of it is copied from Django, under the following license: 4 | 5 | Copyright (c) Django Software Foundation and individual contributors. 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | 1. Redistributions of source code must retain the above copyright notice, 12 | this list of conditions and the following disclaimer. 13 | 14 | 2. Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following disclaimer in the 16 | documentation and/or other materials provided with the distribution. 17 | 18 | 3. Neither the name of Django nor the names of its contributors may be used 19 | to endorse or promote products derived from this software without 20 | specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | """ 33 | 34 | import json 35 | from django import forms 36 | 37 | 38 | class JSONFormField(forms.CharField): 39 | default_error_messages = { 40 | 'invalid': 'Enter a valid JSON.', 41 | } 42 | 43 | def __init__(self, encoder=None, decoder=None, **kwargs): 44 | self.encoder = encoder 45 | self.decoder = decoder 46 | super().__init__(**kwargs) 47 | 48 | def to_python(self, value): 49 | if self.disabled: 50 | return value 51 | if value in self.empty_values: 52 | return None 53 | elif isinstance(value, (list, dict, int, float, JSONString)): 54 | return value 55 | try: 56 | converted = json.loads(value, cls=self.decoder) 57 | except json.JSONDecodeError: 58 | raise forms.ValidationError( 59 | self.error_messages['invalid'], 60 | code='invalid', 61 | params={'value': value}, 62 | ) 63 | if isinstance(converted, str): 64 | return JSONString(converted) 65 | else: 66 | return converted 67 | 68 | def bound_data(self, data, initial): 69 | if self.disabled: 70 | return initial 71 | if data is None: 72 | return None 73 | try: 74 | return json.loads(data, cls=self.decoder) 75 | except json.JSONDecodeError: 76 | return InvalidJSONInput(data) 77 | 78 | def prepare_value(self, value): 79 | if isinstance(value, InvalidJSONInput): 80 | return value 81 | return json.dumps(value, ensure_ascii=False, cls=self.encoder) 82 | 83 | def has_changed(self, initial, data): 84 | if super().has_changed(initial, data): 85 | return True 86 | # For purposes of seeing whether something has changed, True isn't the 87 | # same as 1 and the order of keys doesn't matter. 88 | return ( 89 | json.dumps(initial, sort_keys=True, cls=self.encoder) != 90 | json.dumps(self.to_python(data), sort_keys=True, cls=self.encoder) 91 | ) 92 | 93 | 94 | class InvalidJSONInput(str): 95 | pass 96 | 97 | 98 | class JSONString(str): 99 | pass -------------------------------------------------------------------------------- /django_jsonform/forms/fields.py: -------------------------------------------------------------------------------- 1 | import json 2 | import django 3 | from django.db import models 4 | from django.conf import settings 5 | from django.core.exceptions import ImproperlyConfigured, ValidationError 6 | from django.core.serializers.json import DjangoJSONEncoder 7 | from django_jsonform.utils import _get_django_version 8 | 9 | django_major, django_minor = _get_django_version() 10 | 11 | if django_major > 3 or (django_major == 3 and django_minor >= 1): 12 | # Django >= 3.1 13 | from django.forms import JSONField as DjangoJSONFormField 14 | else: 15 | # Django < 3.1 16 | if 'postgres' in settings.DATABASES['default']['ENGINE']: 17 | from django.contrib.postgres.forms import JSONField as DjangoJSONFormField 18 | else: 19 | from django_jsonform.forms.compat import JSONFormField as DjangoJSONFormField 20 | 21 | try: 22 | from django.contrib.postgres.forms import SimpleArrayField 23 | except ImportError: 24 | class SimpleArrayField: 25 | mock_field = True 26 | 27 | from django_jsonform.widgets import JSONFormWidget 28 | 29 | from django.forms.widgets import TextInput 30 | from django_jsonform.validators import JSONSchemaValidator 31 | from django_jsonform.exceptions import JSONSchemaValidationError 32 | 33 | 34 | class JSONFormField(DjangoJSONFormField): 35 | def __init__( 36 | self, *, schema=None, encoder=None, decoder=None, model_name='', 37 | file_handler='', 38 | **kwargs 39 | ): 40 | self.file_handler = file_handler 41 | if not kwargs.get('widget'): 42 | kwargs['widget'] = JSONFormWidget(schema=schema, model_name=model_name, file_handler=file_handler) 43 | 44 | self.widget = kwargs['widget'] 45 | 46 | super().__init__(**kwargs) 47 | 48 | def validate(self, value): 49 | super().validate(value) 50 | validator = JSONSchemaValidator(schema=self.widget.get_schema()) 51 | try: 52 | validator(value) 53 | except JSONSchemaValidationError as e: 54 | self.add_error(e.error_map) 55 | raise 56 | 57 | def run_validators(self, value): 58 | if value in self.empty_values: 59 | return 60 | errors = [] 61 | for v in self.validators: 62 | try: 63 | v(value) 64 | except (JSONSchemaValidationError, ValidationError) as e: 65 | if hasattr(e, 'error_map'): 66 | self.add_error(e.error_map) 67 | 68 | if hasattr(e, 'code') and e.code in self.error_messages: 69 | e.message = self.error_messages[e.code] 70 | errors.extend(e.error_list) 71 | if errors: 72 | raise ValidationError(errors) 73 | 74 | def add_error(self, error_map): 75 | self.widget.add_error(error_map) 76 | 77 | 78 | class ArrayFormField(SimpleArrayField): 79 | def __init__(self, base_field, **kwargs): 80 | if hasattr(SimpleArrayField, 'mock_field'): 81 | raise ImproperlyConfigured('ArrayField requires psycopg2 to be installed.') 82 | 83 | self.base_field = base_field 84 | self.max_items = kwargs.get('max_length', kwargs.get('size', None)) 85 | self.min_items = kwargs.get('min_length') 86 | 87 | self.nested = kwargs.pop('nested', False) 88 | self.schema = kwargs.pop('schema', None) 89 | 90 | if not self.nested: 91 | self.widget = JSONFormWidget(schema=self.get_schema()) 92 | else: 93 | self.widget = TextInput 94 | 95 | if not kwargs.get('widget'): 96 | kwargs['widget'] = self.widget 97 | 98 | super().__init__(base_field, **kwargs) 99 | 100 | def prepare_value(self, value): 101 | if isinstance(value, list): 102 | return json.dumps(value, cls=DjangoJSONEncoder) 103 | return value 104 | 105 | def to_python(self, value): 106 | if isinstance(value, str): 107 | value = json.loads(value) 108 | return super().to_python(value) 109 | 110 | def get_schema(self): 111 | if self.schema: 112 | return self.schema 113 | 114 | schema = {'type': 'array'} 115 | 116 | if isinstance(self.base_field, ArrayFormField): 117 | items = self.base_field.get_schema() 118 | elif isinstance(self.base_field, django.forms.IntegerField): 119 | items = {'type': 'number'} 120 | elif isinstance(self.base_field, JSONFormField): 121 | items = self.base_field.widget.get_schema() 122 | else: 123 | items = {'type': 'string'} 124 | if isinstance(self.base_field, django.forms.URLField): 125 | items['format'] = 'uri-reference' 126 | 127 | schema['items'] = items 128 | 129 | if self.max_items: 130 | schema['max_items'] = self.max_items 131 | if self.min_items: 132 | schema['min_items'] = self.min_items 133 | 134 | return schema 135 | -------------------------------------------------------------------------------- /django_jsonform/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhch/django-jsonform/e485ef149902962a5b9ec83bcb1150ffbee64ef6/django_jsonform/models/__init__.py -------------------------------------------------------------------------------- /django_jsonform/models/compat.py: -------------------------------------------------------------------------------- 1 | """Compatibility fields for SQLite when Django < 3.1. 2 | 3 | Some of it is copied from Django, under the following license: 4 | 5 | Copyright (c) Django Software Foundation and individual contributors. 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | 1. Redistributions of source code must retain the above copyright notice, 12 | this list of conditions and the following disclaimer. 13 | 14 | 2. Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following disclaimer in the 16 | documentation and/or other materials provided with the distribution. 17 | 18 | 3. Neither the name of Django nor the names of its contributors may be used 19 | to endorse or promote products derived from this software without 20 | specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | """ 33 | 34 | import json 35 | from django.db import models 36 | from django.core import exceptions 37 | from django_jsonform.forms.fields import JSONFormField 38 | 39 | 40 | class JSONField(models.TextField): 41 | def __init__( 42 | self, verbose_name=None, name=None, encoder=None, decoder=None, 43 | **kwargs, 44 | ): 45 | if encoder and not callable(encoder): 46 | raise ValueError('The encoder parameter must be a callable object.') 47 | if decoder and not callable(decoder): 48 | raise ValueError('The decoder parameter must be a callable object.') 49 | self.encoder = encoder 50 | self.decoder = decoder 51 | super().__init__(verbose_name, name, **kwargs) 52 | 53 | def deconstruct(self): 54 | name, path, args, kwargs = super().deconstruct() 55 | if self.encoder is not None: 56 | kwargs['encoder'] = self.encoder 57 | if self.decoder is not None: 58 | kwargs['decoder'] = self.decoder 59 | return name, path, args, kwargs 60 | 61 | def to_python(self, value): 62 | if value == "" or value == None: 63 | return None 64 | 65 | try: 66 | return json.loads(value, cls=self.decoder) 67 | except (json.JSONDecodeError, TypeError): 68 | return value 69 | 70 | def from_db_value(self, value, expression, connection): 71 | return self.to_python(value) 72 | 73 | def get_prep_value(self, value): 74 | if value is None: 75 | return value 76 | return json.dumps(value, cls=self.encoder) 77 | 78 | def validate(self, value, model_instance): 79 | super().validate(value, model_instance) 80 | try: 81 | json.dumps(value, cls=self.encoder) 82 | except TypeError: 83 | raise exceptions.ValidationError( 84 | self.error_messages['invalid'], 85 | code='invalid', 86 | params={'value': value}, 87 | ) 88 | 89 | def value_to_string(self, obj): 90 | return self.value_from_object(obj) 91 | 92 | def formfield(self, **kwargs): 93 | return super().formfield(**{ 94 | 'form_class': JSONFormField, 95 | 'encoder': self.encoder, 96 | 'decoder': self.decoder, 97 | **kwargs, 98 | }) 99 | -------------------------------------------------------------------------------- /django_jsonform/models/fields.py: -------------------------------------------------------------------------------- 1 | import django 2 | from django.conf import settings 3 | from django.core.exceptions import ImproperlyConfigured 4 | from django_jsonform.utils import _get_django_version 5 | 6 | django_major, django_minor = _get_django_version() 7 | 8 | if django_major > 3 or (django_major == 3 and django_minor >= 1): 9 | # Django >= 3.1 10 | from django.db.models import JSONField as DjangoJSONField 11 | else: 12 | # Django < 3.1 13 | if 'postgres' in settings.DATABASES['default']['ENGINE']: 14 | from django.contrib.postgres.fields import JSONField as DjangoJSONField 15 | else: 16 | from django_jsonform.models.compat import JSONField as DjangoJSONField 17 | 18 | try: 19 | from django.contrib.postgres.fields import ArrayField as DjangoArrayField 20 | except ImportError: 21 | class DjangoArrayField: 22 | mock_field = True 23 | 24 | from django_jsonform.forms.fields import JSONFormField 25 | from django_jsonform.forms.fields import ArrayFormField 26 | 27 | 28 | class JSONField(DjangoJSONField): 29 | def __init__(self, *args, **kwargs): 30 | self.schema = kwargs.pop('schema', {}) 31 | self.pre_save_hook = kwargs.pop('pre_save_hook', None) 32 | self.file_handler = kwargs.pop('file_handler', '') 33 | super().__init__(*args, **kwargs) 34 | 35 | def formfield(self, **kwargs): 36 | return super().formfield(**{ 37 | 'form_class': JSONFormField, 38 | 'schema': self.schema, 39 | 'model_name': self.model.__name__, 40 | 'file_handler': self.file_handler, 41 | **kwargs 42 | }) 43 | 44 | def pre_save(self, model_instance, add): 45 | value = super().pre_save(model_instance, add) 46 | 47 | if (self.pre_save_hook): 48 | value = self.pre_save_hook(value) 49 | 50 | return value 51 | 52 | 53 | class ArrayField(DjangoArrayField): 54 | def __init__(self, *args, **kwargs): 55 | if hasattr(DjangoArrayField, 'mock_field'): 56 | raise ImproperlyConfigured('ArrayField requires psycopg2 to be installed.') 57 | 58 | self.nested = kwargs.pop('nested', False) 59 | self.schema = kwargs.pop('schema', None) 60 | super().__init__(*args, **kwargs) 61 | 62 | def formfield(self, **kwargs): 63 | return super().formfield(**{ 64 | 'form_class': ArrayFormField, 65 | 'nested': self.nested, 66 | 'schema': self.schema, 67 | **kwargs, 68 | }) 69 | -------------------------------------------------------------------------------- /django_jsonform/static/django_jsonform/img/arrow-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /django_jsonform/static/django_jsonform/img/collapsed-indicator-bg-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /django_jsonform/static/django_jsonform/img/collapsed-indicator-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /django_jsonform/static/django_jsonform/img/control-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /django_jsonform/static/django_jsonform/img/icon-addlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /django_jsonform/static/django_jsonform/img/icon-changelink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /django_jsonform/static/django_jsonform/img/icon-deletelink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /django_jsonform/static/django_jsonform/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var _initializedCache = []; 3 | 4 | function initJSONForm(element) { 5 | // Check if element has already been initialized 6 | if (_initializedCache.indexOf(element) !== -1) { 7 | return; 8 | } 9 | 10 | var dataInput = element; 11 | var dataInputId = element.id; 12 | 13 | var config = JSON.parse(element.dataset.djangoJsonform); 14 | config.data = JSON.parse(config.data); 15 | 16 | var containerId = element.id + '_jsonform'; 17 | 18 | var container = element.previousElementSibling; 19 | container.setAttribute('id', element.id + '_jsonform'); 20 | 21 | config.containerId = containerId; 22 | config.dataInputId = dataInputId; 23 | 24 | var jsonForm = reactJsonForm.createForm(config); 25 | 26 | if (config.validateOnSubmit) { 27 | var form = dataInput.form; 28 | form.addEventListener('submit', function(e) { 29 | var errorlist = container.parentElement.previousElementSibling; 30 | var hasError; 31 | 32 | if (errorlist && errorlist.classList.contains('errorlist')) 33 | hasError = true; 34 | else 35 | hasError = false; 36 | 37 | var validation = jsonForm.validate(); 38 | 39 | if (!validation.isValid) { 40 | e.preventDefault(); 41 | 42 | if (!hasError) { 43 | errorlist = document.createElement('ul'); 44 | errorlist.setAttribute('class', 'errorlist'); 45 | var errorli = document.createElement('li'); 46 | errorli.textContent = 'Please correct the errors below.'; 47 | errorlist.appendChild(errorli); 48 | 49 | container.parentElement.parentElement.insertBefore( 50 | errorlist, container.parentElement 51 | ); 52 | } 53 | 54 | errorlist.scrollIntoView(); 55 | } else { 56 | if (hasError) 57 | errorlist.remove(); 58 | } 59 | jsonForm.update({ errorMap: validation.errorMap }); 60 | }); 61 | } 62 | jsonForm.render(); 63 | _initializedCache.push(element); 64 | } 65 | 66 | /** 67 | * Helper function to determine if the element is being dragged, so that we 68 | * don't initialize the json form fields. They will get initialized when the dragging stops. 69 | * 70 | * @param element The element to check 71 | * @returns {boolean} 72 | */ 73 | function isDraggingElement(element) { 74 | return 'classList' in element && element.classList.contains('ui-sortable-helper'); 75 | } 76 | 77 | function initializeAllForNode(parentElement) { 78 | if (parentElement.querySelectorAll === undefined) 79 | return; 80 | 81 | var containers = parentElement.querySelectorAll('[data-django-jsonform]'); 82 | 83 | // hacky way to filter NodeList using Array.filter 84 | [].filter.call(containers, function(container) { 85 | 86 | // filter out elements that contain '__prefix__' in their id 87 | // these are used by django formsets for template forms 88 | if (container.id.indexOf('__prefix__') > -1) 89 | return false; 90 | 91 | // filter out elements that contain '-empty-' in their ids 92 | // these are used by django-nested-admin for nested template formsets 93 | // also ensure that 'empty' is not actually the related_name for some relation 94 | // by checking that it is not surrounded by numbers on both sides 95 | if (container.id.match(/-empty-/) && !container.id.match(/-\d+-empty-\d+-/)) 96 | return false; 97 | 98 | return true; 99 | }) 100 | .forEach(initJSONForm); 101 | } 102 | 103 | function init() { 104 | // Initialize all json form fields already on the page. 105 | initializeAllForNode(document); 106 | 107 | // Setup listeners to initialize all json form fields as they get added to the page. 108 | if ('MutationObserver' in window) { 109 | new MutationObserver(function(mutations) { 110 | var mutationRecord; 111 | var addedNode; 112 | 113 | for (var i = 0; i < mutations.length; i++) { 114 | mutationRecord = mutations[i]; 115 | 116 | if (mutationRecord.addedNodes.length > 0) { 117 | for (var j = 0; j < mutationRecord.addedNodes.length; j++) { 118 | 119 | addedNode = mutationRecord.addedNodes[j]; 120 | 121 | if (isDraggingElement(addedNode)) 122 | return; 123 | 124 | initializeAllForNode(addedNode); 125 | } 126 | } 127 | } 128 | }).observe(document.documentElement, { childList: true, subtree: true }); 129 | } else { 130 | document.addEventListener('DOMNodeInserted', function(e) { 131 | if (isDraggingElement(e.target)) 132 | return; 133 | 134 | initializeAllForNode(e.target); 135 | }); 136 | } 137 | } 138 | 139 | if (document.readyState === 'interactive' || 140 | document.readyState === 'complete' || 141 | document.readyState === 'loaded') { 142 | init(); 143 | } else { 144 | document.addEventListener('DOMContentLoaded', function() { 145 | init(); 146 | }); 147 | } 148 | 149 | })(); 150 | -------------------------------------------------------------------------------- /django_jsonform/static/django_jsonform/vendor/react.production.min.js: -------------------------------------------------------------------------------- 1 | /** @license React v17.0.2 2 | * react.production.min.js 3 | * 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | */ 9 | (function(){'use strict';(function(c,x){"object"===typeof exports&&"undefined"!==typeof module?x(exports):"function"===typeof define&&define.amd?define(["exports"],x):(c=c||self,x(c.React={}))})(this,function(c){function x(a){if(null===a||"object"!==typeof a)return null;a=Y&&a[Y]||a["@@iterator"];return"function"===typeof a?a:null}function y(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,e=1;e>>1,f=a[c];if(void 0!==f&&0E(g,e))void 0!==h&&0>E(h,g)?(a[c]=h,a[k]=e,c=k):(a[c]=g,a[d]=e,c=d);else if(void 0!==h&&0>E(h,e))a[c]=h,a[k]=e,c=k;else break a}}return b}return null}function E(a,b){var e=a.sortIndex-b.sortIndex;return 0!==e?e:a.id-b.id}function P(a){for(var b=p(r);null!==b;){if(null===b.callback)F(r);else if(b.startTime<=a)F(r),b.sortIndex=b.expirationTime,O(q,b);else break;b=p(r)}} 16 | function Q(a){z=!1;P(a);if(!u)if(null!==p(q))u=!0,A(R);else{var b=p(r);null!==b&&G(Q,b.startTime-a)}}function R(a,b){u=!1;z&&(z=!1,S());H=!0;var e=g;try{P(b);for(m=p(q);null!==m&&(!(m.expirationTime>b)||a&&!T());){var c=m.callback;if("function"===typeof c){m.callback=null;g=m.priorityLevel;var f=c(m.expirationTime<=b);b=t();"function"===typeof f?m.callback=f:m===p(q)&&F(q);P(b)}else F(q);m=p(q)}if(null!==m)var d=!0;else{var n=p(r);null!==n&&G(Q,n.startTime-b);d=!1}return d}finally{m=null,g=e,H=!1}} 17 | var w=60103,ha=60106;c.Fragment=60107;c.StrictMode=60108;c.Profiler=60114;var ka=60109,la=60110,ma=60112;c.Suspense=60113;var na=60115,oa=60116;if("function"===typeof Symbol&&Symbol.for){var d=Symbol.for;w=d("react.element");ha=d("react.portal");c.Fragment=d("react.fragment");c.StrictMode=d("react.strict_mode");c.Profiler=d("react.profiler");ka=d("react.provider");la=d("react.context");ma=d("react.forward_ref");c.Suspense=d("react.suspense");na=d("react.memo");oa=d("react.lazy")}var Y="function"=== 18 | typeof Symbol&&Symbol.iterator,ya=Object.prototype.hasOwnProperty,U=Object.assign||function(a,b){if(null==a)throw new TypeError("Object.assign target cannot be null or undefined");for(var e=Object(a),c=1;c=ta};d=function(){};V=function(a){0>a||125d?(a.sortIndex= 25 | c,O(r,a),null===p(q)&&a===p(r)&&(z?S():z=!0,G(Q,c-d))):(a.sortIndex=e,O(q,a),u||H||(u=!0,A(R)));return a},unstable_cancelCallback:function(a){a.callback=null},unstable_wrapCallback:function(a){var b=g;return function(){var c=g;g=b;try{return a.apply(this,arguments)}finally{g=c}}},unstable_getCurrentPriorityLevel:function(){return g},get unstable_shouldYield(){return T},unstable_requestPaint:d,unstable_continueExecution:function(){u||H||(u=!0,A(R))},unstable_pauseExecution:function(){},unstable_getFirstCallbackNode:function(){return p(q)}, 26 | get unstable_now(){return t},get unstable_forceFrameRate(){return V},unstable_Profiling:null},SchedulerTracing:{__proto__:null,__interactionsRef:null,__subscriberRef:null,unstable_clear:function(a){return a()},unstable_getCurrent:function(){return null},unstable_getThreadID:function(){return++Ea},unstable_trace:function(a,b,c){return c()},unstable_wrap:function(a){return a},unstable_subscribe:function(a){},unstable_unsubscribe:function(a){}}};c.Children={map:D,forEach:function(a,b,c){D(a,function(){b.apply(this, 27 | arguments)},c)},count:function(a){var b=0;D(a,function(){b++});return b},toArray:function(a){return D(a,function(a){return a})||[]},only:function(a){if(!M(a))throw Error(y(143));return a}};c.Component=v;c.PureComponent=K;c.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=d;c.cloneElement=function(a,b,c){if(null===a||void 0===a)throw Error(y(267,a));var d=U({},a.props),e=a.key,g=a.ref,n=a._owner;if(null!=b){void 0!==b.ref&&(g=b.ref,n=L.current);void 0!==b.key&&(e=""+b.key);if(a.type&&a.type.defaultProps)var k= 28 | a.type.defaultProps;for(h in b)ea.call(b,h)&&!fa.hasOwnProperty(h)&&(d[h]=void 0===b[h]&&void 0!==k?k[h]:b[h])}var h=arguments.length-2;if(1===h)d.children=c;else if(1 2 | 3 | 4 | -------------------------------------------------------------------------------- /django_jsonform/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhch/django-jsonform/e485ef149902962a5b9ec83bcb1150ffbee64ef6/django_jsonform/templatetags/__init__.py -------------------------------------------------------------------------------- /django_jsonform/templatetags/django_jsonform.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.utils import timezone 3 | from datetime import time 4 | 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.filter 10 | def parse_datetime(value): 11 | """Converts ISO date string to a datetime object 12 | which can be used with the ``date`` filter. 13 | """ 14 | try: 15 | return timezone.datetime.fromisoformat(value) 16 | except ValueError: 17 | return value 18 | 19 | 20 | @register.filter 21 | def parse_time(value): 22 | """Converts a time string (24-hour format) to a time object 23 | whch can be used with the ``time`` filter. 24 | """ 25 | return time(*[int(x) for x in value.split(':')]) 26 | -------------------------------------------------------------------------------- /django_jsonform/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /django_jsonform/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | 5 | app_name = 'django_jsonform' 6 | 7 | urlpatterns = [ 8 | path('upload/', views.upload_handler, name='upload'), 9 | ] -------------------------------------------------------------------------------- /django_jsonform/utils.py: -------------------------------------------------------------------------------- 1 | import django 2 | from django.utils.functional import Promise 3 | from django.conf import settings 4 | from django_jsonform.constants import JOIN_SYMBOL 5 | import itertools 6 | import string 7 | 8 | 9 | def normalize_schema(schema): 10 | """Prepares a schema for converting to JSON. 11 | 12 | We allow python functions in the schema but they aren't 13 | JSON serializable. This function transforms the schema to 14 | make it a valid JSON object. 15 | 16 | Eg: Processing lazy translations, etc. 17 | """ 18 | 19 | if isinstance(schema, dict): 20 | new_schema = {} 21 | for key, value in schema.items(): 22 | if isinstance(value, (dict, list)): 23 | value = normalize_schema(value) 24 | elif isinstance(value, Promise): 25 | value = str(value) 26 | new_schema[key] = value 27 | elif isinstance(schema, list): 28 | new_schema = [] 29 | for value in schema: 30 | if isinstance(value, (dict, list)): 31 | value = normalize_schema(value) 32 | elif isinstance(value, Promise): 33 | value = str(value) 34 | new_schema.append(value) 35 | else: 36 | new_schema = {} 37 | 38 | return new_schema 39 | 40 | 41 | def normalize_keyword(kw): 42 | """Converts custom keywords to standard JSON schema keywords""" 43 | return normalize_keyword.kw_map.get(kw, kw) 44 | 45 | normalize_keyword.kw_map = { 46 | 'list': 'array', 47 | 'dict': 'object', 48 | 'keys': 'properties', 49 | 'choices': 'enum', 50 | 'datetime': 'date-time' 51 | } 52 | 53 | def get_schema_type(schema): 54 | """Returns the normalized type of schema. 55 | 56 | Will try to sensibly discern the type if 'type' keyword is not present. 57 | 58 | Will return None on failure to guess. 59 | """ 60 | typ = schema.get('type', None) 61 | 62 | if 'const' in schema: 63 | return 'const' 64 | 65 | if isinstance(typ, list): 66 | # find first element that's not null or None 67 | typ = next(filter(lambda x: x not in ['null', None, 'None', 'none'], typ), None) 68 | 69 | if typ is None: 70 | if 'properties' in schema or 'keys' in schema: 71 | # if schema has 'properties' or 'keys' keyword 72 | # it must be an object 73 | typ = 'object' 74 | elif 'items' in schema: 75 | # if schema as 'items' keyword 76 | # it must be an array 77 | typ = 'array' 78 | 79 | return normalize_keyword(typ) 80 | 81 | 82 | def get_setting(name, default=None): 83 | """Returns settings nested inside DJANGO_JSONFORM main setting variable""" 84 | if not hasattr(settings, 'DJANGO_JSONFORM'): 85 | return default 86 | 87 | return settings.DJANGO_JSONFORM.get(name, default) 88 | 89 | 90 | def join_coords(*coords): 91 | return JOIN_SYMBOL.join([str(coord) for coord in coords]).strip(JOIN_SYMBOL) 92 | 93 | 94 | def split_coords(coords): 95 | return coords.split(JOIN_SYMBOL) 96 | 97 | 98 | class ErrorMap(dict): 99 | def set(self, coords, msg): 100 | key = join_coords(*coords) 101 | 102 | if not isinstance(msg, list): 103 | msg = [msg] 104 | 105 | self[key] = msg 106 | 107 | def append(self, coords, msg): 108 | key = join_coords(*coords) 109 | 110 | if key not in self: 111 | self.set(coords, msg) 112 | return 113 | 114 | if not isinstance(msg, list): 115 | msg = [msg] 116 | 117 | self[key] = self[key] + msg 118 | 119 | 120 | def _get_django_version(): 121 | """Returns django version as a 2-tuple of (major, minor) segments. 122 | 123 | It disregards the patch, alpha, beta, rc, etc. identifiers. 124 | 125 | For internal use only. 126 | """ 127 | return tuple( 128 | map( 129 | int, 130 | ''.join( 131 | itertools.takewhile( 132 | lambda x: x not in string.ascii_letters, django.get_version() 133 | ) 134 | ).split('.')[:2] 135 | ) 136 | ) 137 | -------------------------------------------------------------------------------- /django_jsonform/views.py: -------------------------------------------------------------------------------- 1 | from importlib import import_module 2 | from django.http import JsonResponse, HttpResponseNotAllowed 3 | from django.conf import settings 4 | from django.contrib.auth.decorators import login_required 5 | 6 | 7 | if hasattr(settings, 'JSONFORM_UPLOAD_HANDLER'): 8 | # 'JSONFORM_UPLOAD_HANDLER' setting is deprecated. 9 | # We still do this check for backwards compatibility. 10 | module_path, handler_func = settings.JSONFORM_UPLOAD_HANDLER.rsplit('.', 1) 11 | FILE_UPLOAD_HANDLER = getattr(import_module(module_path), handler_func) 12 | else: 13 | FILE_UPLOAD_HANDLER = None 14 | 15 | 16 | @login_required 17 | def upload_handler(request): 18 | if request.method == 'POST': 19 | if not FILE_UPLOAD_HANDLER: 20 | return JsonResponse({'error': 'File handler not provided'}) 21 | file_path = FILE_UPLOAD_HANDLER(request) 22 | return JsonResponse({'value': file_path}) 23 | elif request.method == 'GET': 24 | return JsonResponse({'results': []}) 25 | return HttpResponseNotAllowed(['POST'], '405 Method Not Allowed') 26 | -------------------------------------------------------------------------------- /django_jsonform/widgets.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import json 3 | from inspect import signature 4 | from django import forms 5 | from django.template.loader import render_to_string 6 | from django.utils.safestring import mark_safe 7 | from django_jsonform.utils import normalize_schema, get_setting 8 | from django.urls import reverse, NoReverseMatch 9 | 10 | 11 | class JSONFormWidget(forms.Widget): 12 | template_name = 'django_jsonform/editor.html' 13 | 14 | def __init__( 15 | self, 16 | schema, 17 | model_name='', 18 | file_handler='', 19 | validate_on_submit=False, 20 | attrs=None, 21 | ): 22 | super().__init__(attrs=attrs) 23 | 24 | self.schema = schema 25 | self.model_name = model_name 26 | self.file_handler = file_handler 27 | self.validate_on_submit = validate_on_submit 28 | 29 | def get_schema(self): 30 | """Returns the schema attached to this widget. 31 | 32 | If the schema is a callable, it will return the result of the callable. 33 | """ 34 | if callable(self.schema): 35 | if hasattr(self, 'instance') and len(signature(self.schema).parameters): 36 | schema = self.schema(self.instance) 37 | else: 38 | schema = self.schema() 39 | else: 40 | schema = self.schema 41 | 42 | return schema 43 | 44 | def render(self, name, value, attrs=None, renderer=None): 45 | schema = normalize_schema(self.get_schema()) 46 | 47 | context = self.get_context(name, value, attrs) 48 | 49 | if attrs is None: 50 | attrs = {} 51 | 52 | context['widget'].update({ 53 | 'config': { 54 | 'data': value or json.dumps(''), 55 | 'schema': schema, 56 | 'fileHandler': self.file_handler or get_setting('FILE_HANDLER', ''), 57 | 'fileHandlerArgs': { 58 | 'field_name': context['widget']['name'], 59 | 'model_name': self.model_name, 60 | }, 61 | 'errorMap': getattr(self, 'error_map', {}), 62 | 'validateOnSubmit': self.validate_on_submit, 63 | 'readonly': attrs.get('disabled', False), 64 | }, 65 | }) 66 | 67 | # backwards compatibility for `JSONFORM_UPLOAD_HANDLER` setting 68 | if not context['widget']['config']['fileHandler']: 69 | try: 70 | context['widget']['config']['fileHandler'] = reverse('django_jsonform:upload') 71 | except NoReverseMatch: 72 | pass 73 | 74 | # Turn widget config into json string 75 | context['widget']['config'] = json.dumps(context['widget']['config']) 76 | 77 | html_container_class = 'django-jsonform-container' 78 | if 'class' in context['widget']['attrs']: 79 | context['widget']['attrs']['class'] = '%s %s' % (context['widget']['attrs']['class'], html_container_class) 80 | else: 81 | context['widget']['attrs'].update({'class': html_container_class}) 82 | 83 | return mark_safe(render_to_string(self.template_name, context)) 84 | 85 | def add_error(self, error_map): 86 | if not hasattr(self, 'error_map'): 87 | setattr(self, 'error_map', copy.deepcopy(error_map)) 88 | return 89 | 90 | # if here, this is being called more than once 91 | # therefore, extend the current error_map 92 | for key in error_map: 93 | if key in self.error_map: 94 | if not isinstance(self.error_map[key], list): 95 | self.error_map[key] = [self.error_map[key]] 96 | if isinstance(error_map[key], list): 97 | self.error_map[key] += error_map[key] 98 | else: 99 | self.error_map[key].append(error_map[key]) 100 | else: 101 | self.error_map[key] = error_map[key] 102 | 103 | @property 104 | def media(self): 105 | css = { 106 | 'all': [ 107 | 'django_jsonform/style.css', 108 | ] 109 | } 110 | js = [ 111 | 'django_jsonform/vendor/react.production.min.js', 112 | 'django_jsonform/vendor/react-dom.production.min.js', 113 | 'django_jsonform/vendor/react-modal.min.js', 114 | 'django_jsonform/react-json-form.js', 115 | 'django_jsonform/index.js', 116 | ] 117 | 118 | return forms.Media(css=css, js=js) 119 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_ext/helpers.py: -------------------------------------------------------------------------------- 1 | def setup(app): 2 | app.add_crossref_type( 3 | directivename='setting', 4 | rolename='setting', 5 | indextemplate='pair: %s; setting', 6 | ) 7 | app.add_crossref_type( 8 | directivename='templatetag', 9 | rolename='ttag', 10 | indextemplate='pair: %s; template tag', 11 | ) 12 | app.add_crossref_type( 13 | directivename='templatefilter', 14 | rolename='tfilter', 15 | indextemplate='pair: %s; template filter', 16 | ) 17 | 18 | return { 19 | 'version': '0.1', 20 | 'parallel_read_safe': True, 21 | 'parallel_write_safe': True, 22 | } 23 | -------------------------------------------------------------------------------- /docs/_static/autocomplete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhch/django-jsonform/e485ef149902962a5b9ec83bcb1150ffbee64ef6/docs/_static/autocomplete.gif -------------------------------------------------------------------------------- /docs/_static/candy.css: -------------------------------------------------------------------------------- 1 | /* 2 | * candy.css 3 | * --------- 4 | * 5 | * Overrides sphinx_rtd_theme. 6 | * 7 | * :copyright: Copyright 2021 Bharat Chauhan 8 | * :license: BSD-3-Clause 9 | * 10 | */ 11 | 12 | /* 13 | 14 | Colours 15 | ------- 16 | 17 | $text-color: #323130; 18 | 19 | $color-primary-shade-30: #004a41; 20 | $color-primary-shade-20: #006256; 21 | $color-primary-shade-10: #0a766c; 22 | $color-primary: #008374; 23 | $color-primary-tint-10: #1e958a; 24 | $color-primary-tint-20: #c9f2ed; 25 | $color-primary-tint-30: #dff8f6; 26 | $color-primary-tint-40: #f0fbfa; 27 | 28 | $color-accent-shade-20: #be1932; 29 | $color-accent-shade-10: #d7213c; 30 | $color-accent: #ea2643; 31 | $color-accent-tint-10: #ff3855; 32 | 33 | $color-black: #000000; 34 | $color-gray-190: #201f1e; 35 | $color-gray-160: #323130; 36 | $color-gray-150: #3b3a39; 37 | $color-gray-130: #605e5c; 38 | $color-gray-120: #797775; 39 | $color-gray-90: #a19f9d; 40 | $color-gray-60: #c8c6c4; 41 | $color-gray-50: #d2d0ce; 42 | $color-gray-40: #e1dfdd; 43 | $color-gray-30: #edebe9; 44 | $color-gray-20: #f3f2f1; 45 | $color-gray-10: #faf9f8; 46 | $color-white: #ffffff; 47 | 48 | $color-error-dark : #d83b01; 49 | $color-error-light : #f55215; 50 | $color-success-dark : #107c10; 51 | $color-success-light : #3cb43c; 52 | */ 53 | 54 | 55 | body { 56 | font-family: Inter, 'Open Sans', Roboto, Lato, proxima-nova, Helvetica Neue, 57 | Arial, sans-serif; 58 | color: #323130; 59 | } 60 | 61 | .rst-content .toctree-wrapper > p.caption, 62 | h1, h2, h3, h4, h5, h6, legend { 63 | font-family: 'IBM Plex Sans', Roboto, 'Roboto Slab', ff-tisa-web-pro, 64 | Georgia, Arial, sans-serif; 65 | font-weight: 500; 66 | } 67 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code, legend code { 68 | font-weight: 700; 69 | } 70 | 71 | input[type="color"], 72 | input[type="date"], 73 | input[type="datetime-local"], 74 | input[type="datetime"], 75 | input[type="email"], 76 | input[type="month"], 77 | input[type="number"], 78 | input[type="password"], 79 | input[type="search"], 80 | input[type="tel"], 81 | input[type="text"], 82 | input[type="time"], 83 | input[type="url"], 84 | input[type="week"] { 85 | font-family: inherit; 86 | box-shadow: none; 87 | } 88 | 89 | a { 90 | text-decoration: none; 91 | } 92 | a, a:visited { 93 | color: #ea2643; 94 | } 95 | 96 | a:hover { 97 | color: #be1932; 98 | text-decoration: underline; 99 | } 100 | 101 | hr { 102 | border-color: #e1dfdd; 103 | } 104 | 105 | .wy-body-for-nav { 106 | background-color: #f3f2f1; 107 | } 108 | 109 | .wy-nav-top { 110 | background: #ff373b; 111 | } 112 | .wy-nav-top a, 113 | .wy-nav-top a:visited { 114 | color: #fff; 115 | text-decoration: none; 116 | } 117 | 118 | .wy-side-nav-search .wy-dropdown > a img.logo, 119 | .wy-side-nav-search > a img.logo { 120 | max-width: 150px; 121 | } 122 | .wy-side-nav-search { 123 | background-color: transparent; 124 | } 125 | .wy-side-nav-search input[type="text"] { 126 | border-color: #d2d0ce; 127 | border-radius: 4px; 128 | } 129 | .wy-side-nav-search input[type="text"]:focus { 130 | border-color: #008374; 131 | } 132 | .wy-nav-side { 133 | background-color: #f3f2f1; 134 | border-right: 1px solid #e1dfdd; 135 | } 136 | .wy-menu-vertical header, 137 | .wy-menu-vertical p.caption, 138 | .wy-side-nav-search > div.version { 139 | color: #a19f9d; 140 | } 141 | .wy-menu-vertical a { 142 | color: #323130; 143 | font-family: 'IBM Plex Sans', sans-serif; 144 | border-left: 4px solid transparent; 145 | font-size: 95%; 146 | text-decoration: none; 147 | } 148 | .wy-menu-vertical a, 149 | .wy-menu-vertical li.current > a, 150 | .wy-menu-vertical li.on a { 151 | padding-top: 0.7em; 152 | padding-bottom: 0.7em; 153 | } 154 | .wy-menu-vertical a:hover, 155 | .wy-menu-vertical li.current a:hover { 156 | background-color: #e1dfdd; 157 | } 158 | .wy-menu-vertical a:active { 159 | color: #fff; 160 | background-color: #ff3c37; 161 | } 162 | span.toctree-expand { 163 | color: inherit !important; 164 | } 165 | .wy-menu-vertical li.current > a, 166 | .wy-menu-vertical li.on a { 167 | color: #ea2643; 168 | font-weight: normal; 169 | } 170 | .wy-menu-vertical li.current a { 171 | border-right: none; 172 | } 173 | .wy-menu-vertical li.toctree-l1.current > a { 174 | border-top: none; 175 | border-bottom: none; 176 | background: linear-gradient(to bottom right, #ff5722, #ff1259); 177 | color: #fff; 178 | } 179 | .wy-menu-vertical li.current { 180 | background: #f0fbfa; 181 | } 182 | .wy-menu-vertical li.toctree-l2.current > a, 183 | .wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a { 184 | background-color: transparent; 185 | } 186 | .wy-menu-vertical li.toctree-l3.current > a, 187 | .wy-menu-vertical li.toctree-l3.current li.toctree-l4 > a { 188 | background-color: transparent; 189 | } 190 | 191 | .wy-nav-content-wrap { 192 | background: transparent; 193 | } 194 | .wy-nav-content { 195 | background-color: #fcfcfc; 196 | border-right: 1px solid #e1dfdd; 197 | padding: 25px 50px; 198 | padding-bottom: 0; 199 | min-height: 100vh; 200 | position: relative; 201 | display: -webkit-box; 202 | display: -webkit-flex; 203 | display: -ms-flexbox; 204 | display: flex; 205 | -webkit-box-orient: vertical; 206 | -webkit-box-direction: normal; 207 | -webkit-flex-direction: column; 208 | -ms-flex-direction: column; 209 | flex-direction: column; 210 | } 211 | 212 | .wy-nav-content > .rst-content { 213 | display: -webkit-box; 214 | display: -webkit-flex; 215 | display: -ms-flexbox; 216 | display: flex; 217 | -webkit-box-orient: vertical; 218 | -webkit-box-direction: normal; 219 | -webkit-flex-direction: column; 220 | -ms-flex-direction: column; 221 | flex-direction: column; 222 | -webkit-box-flex: 1; 223 | -webkit-flex: 1 0 auto; 224 | -ms-flex: 1 0 auto; 225 | flex: 1 0 auto; height: 100%; 226 | min-height: 100%; 227 | } 228 | .wy-breadcrumbs { 229 | font-size: 85%; 230 | } 231 | 232 | .wy-nav-content .rst-content > .document { 233 | -webkit-box-flex: 1; 234 | -webkit-flex: 1 0 auto; 235 | -ms-flex: 1 0 auto; 236 | flex: 1 0 auto; 237 | margin-bottom: 25px; 238 | } 239 | 240 | /* remove flex from these elements to avoid shrinking */ 241 | .wy-nav-content .rst-content > div[role="navigation"], 242 | .wy-nav-content .rst-content > footer { 243 | -webkit-box-flex: 0; 244 | -webkit-flex: none; 245 | -ms-flex: none; 246 | flex: none; 247 | } 248 | 249 | div.deprecated { 250 | background: #ffdec6; 251 | padding: 10px; 252 | margin-bottom: 24px; 253 | border-radius: 2px; 254 | } 255 | div.deprecated p:last-child { 256 | margin-bottom: 0; 257 | } 258 | div.admonition { 259 | margin-left: -50px; 260 | margin-right: -50px; 261 | padding: 25px 50px !important; 262 | border-top: 1px solid #e1dfdd; 263 | border-bottom: 1px solid #e1dfdd; 264 | } 265 | p.admonition-title { 266 | background-color: transparent !important; 267 | color: inherit !important; 268 | } 269 | .admonition.warning, 270 | .admonition.danger, 271 | .admonition.error, 272 | .admonition.caution, 273 | .admonition.attention, 274 | .admonition.important { 275 | background: #ffdec6; 276 | color: #510f00; 277 | border-color: #eed3c0; 278 | } 279 | 280 | .admonition code { 281 | background-color: rgba(255, 255, 255, 0.55); 282 | } 283 | .admonition a { 284 | text-decoration: underline dotted; 285 | } 286 | .admonition a:hover { 287 | text-decoration: underline; 288 | } 289 | 290 | .btn, 291 | .btn-neutral, 292 | .btn-neutral:visited { 293 | border: 1px solid #ea2643; 294 | color: #ea2643 !important; 295 | background-color: #fff !important; 296 | box-shadow: none; 297 | } 298 | .btn:hover, 299 | .btn-neutral:hover, 300 | .btn-neutral:visited:hover { 301 | color: #fff !important; 302 | background-color: #ea2643 !important; 303 | box-shadow: none; 304 | text-decoration: none; 305 | } 306 | 307 | .rst-content .highlighted { 308 | background: #fffbbb; 309 | box-shadow: none; 310 | } 311 | 312 | .rst-content .toctree-wrapper ul li.toctree-l1 { 313 | margin-bottom: 8px; 314 | } 315 | .rst-content .toctree-wrapper ul li.toctree-l1 ul li a { 316 | font-size: 0.9em; 317 | color: #6e312a; 318 | text-decoration: underline dotted; 319 | } 320 | .rst-content .toctree-wrapper ul li.toctree-l1 ul li a:hover { 321 | color: #be1932; 322 | text-decoration: underline solid; 323 | } 324 | 325 | .rst-content .section ol li > ol, 326 | .rst-content .section ol li > ul, 327 | .rst-content .section ul li > ol, 328 | .rst-content .section ul li > ul { 329 | margin-top: 0; 330 | } 331 | 332 | .rst-content .code-block-caption .headerlink::after, 333 | .rst-content .toctree-wrapper > p.caption:hover .headerlink::after, 334 | .rst-content dl dt .headerlink::after, 335 | .rst-content h1 .headerlink::after, 336 | .rst-content h2 .headerlink::after, 337 | .rst-content h3 .headerlink::after, 338 | .rst-content h4 .headerlink::after, 339 | .rst-content h5 .headerlink::after, 340 | .rst-content h6 .headerlink::after, 341 | .rst-content p.caption .headerlink::after, 342 | .rst-content table > caption .headerlink::after { 343 | display: none; 344 | } 345 | 346 | .rst-content .code-block-caption .headerlink, 347 | .rst-content .toctree-wrapper > p.caption .headerlink, 348 | .rst-content dl dt .headerlink, 349 | .rst-content h1 .headerlink, 350 | .rst-content h2 .headerlink, 351 | .rst-content h3 .headerlink, 352 | .rst-content h4 .headerlink, 353 | .rst-content h5 .headerlink, 354 | .rst-content h6 .headerlink, 355 | .rst-content p.caption .headerlink, 356 | .rst-content table > caption .headerlink { 357 | margin-left: 0.25em; 358 | font-size: 1em; 359 | color: #a19f9d; 360 | text-decoration: none; 361 | } 362 | 363 | .rst-content .code-block-caption .headerlink:hover, 364 | .rst-content .toctree-wrapper > p.caption .headerlink:hover, 365 | .rst-content dl dt .headerlink:hover, 366 | .rst-content h1 .headerlink:hover, 367 | .rst-content h2 .headerlink:hover, 368 | .rst-content h3 .headerlink:hover, 369 | .rst-content h4 .headerlink:hover, 370 | .rst-content h5 .headerlink:hover, 371 | .rst-content h6 .headerlink:hover, 372 | .rst-content p.caption .headerlink:hover, 373 | .rst-content table > caption .headerlink:hover { 374 | color: #008374; 375 | } 376 | 377 | .rst-content .code-block-caption:hover .headerlink, 378 | .rst-content .toctree-wrapper > p.caption:hover .headerlink, 379 | .rst-content dl dt:hover .headerlink, 380 | .rst-content h1:hover .headerlink, 381 | .rst-content h2:hover .headerlink, 382 | .rst-content h3:hover .headerlink, 383 | .rst-content h4:hover .headerlink, 384 | .rst-content h5:hover .headerlink, 385 | .rst-content h6:hover .headerlink, 386 | .rst-content p.caption:hover .headerlink, 387 | .rst-content table > caption:hover .headerlink { 388 | visibility: visible; 389 | } 390 | 391 | p:not(.topic-title) + ul, 392 | p:not(.topic-title) + ol, 393 | p + .contents { 394 | margin-top: -16px; 395 | } 396 | 397 | html.writer-html5 .rst-content table.docutils th { 398 | border: 1px solid #e1dfdd; 399 | } 400 | .rst-content table.docutils thead th, 401 | .rst-content table.field-list thead th, 402 | .wy-table thead th { 403 | color: #605e5c; 404 | } 405 | .rst-content table.docutils td, 406 | .wy-table-bordered-all td { 407 | border-bottom: 1px solid #e1dfdd; 408 | border-left: 1px solid #e1dfdd; 409 | } 410 | .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td, 411 | .wy-table-backed, 412 | .wy-table-odd td, 413 | .wy-table-striped tr:nth-child(2n-1) td { 414 | background-color: #faf9f8; 415 | } 416 | 417 | td > .line-block:last-child { 418 | margin-bottom: 0; 419 | } 420 | 421 | dl { 422 | margin-bottom: 1.5rem; 423 | margin-top: 1.5rem; 424 | } 425 | dt { 426 | margin-bottom: 0.5rem; 427 | } 428 | dd p { 429 | margin-bottom: 0.5rem; 430 | } 431 | html.writer-html4 .rst-content dl:not(.docutils) > dt, 432 | html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) > dt { 433 | background: none; 434 | color: #008374; 435 | border: none; 436 | } 437 | html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list) > dt, 438 | html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list) > dt { 439 | background: none; 440 | color: #008374; 441 | border: none; 442 | } 443 | 444 | .rst-content code, .rst-content tt { 445 | color: #008374; 446 | } 447 | .rst-content code, 448 | .rst-content tt, 449 | code { 450 | border: none; 451 | background-color: none; 452 | } 453 | 454 | a > .rst-content code, 455 | a > .rst-content tt, 456 | a > code { 457 | color: #d7213c !important; 458 | } 459 | 460 | a:hover > .rst-content code, 461 | a:hover > .rst-content tt, 462 | a:hover > code { 463 | background-color: #dff8f6; 464 | } 465 | 466 | .rst-content code, 467 | .rst-content tt, 468 | code, 469 | kbd, 470 | pre, 471 | samp, 472 | .rst-content .linenodiv pre, 473 | .rst-content div[class^="highlight"] pre, 474 | .rst-content pre.literal-block { 475 | font-family: 'IBM Plex Mono', SFMono-Regular, Menlo, Monaco, Consolas, 476 | Liberation Mono, monospace; 477 | } 478 | 479 | .rst-content code.literal, 480 | .rst-content tt.literal { 481 | color: #0a766c; 482 | font-size: 90%; 483 | } 484 | 485 | .highlight { 486 | background-color: transparent; 487 | } 488 | 489 | .rst-content div[class^="highlight"], 490 | .rst-content pre.literal-block { 491 | margin-left: -50px; 492 | margin-right: -50px; 493 | padding: 30px 50px; 494 | border-top: 1px solid #e1dfdd; 495 | border-bottom: 1px solid #e1dfdd; 496 | border-right: none; 497 | border-left: none; 498 | border-radius: 0; 499 | background-color: #faf9f8; 500 | overflow: auto; 501 | } 502 | 503 | .rst-content dl div[class^="highlight"], 504 | .rst-content dl pre.literal-block { 505 | margin-left: 0; 506 | margin-right: 0; 507 | padding: 15px 15px; 508 | border: 1px solid #e1dfdd; 509 | border-radius: 2px; 510 | } 511 | 512 | .rst-content div[class^="highlight"] > div.highlight { 513 | overflow: visible; 514 | } 515 | 516 | .rst-content div[class^="highlight"] pre { 517 | padding: 0; 518 | overflow: visible; 519 | } 520 | 521 | .rst-content .linenodiv pre, 522 | .rst-content div[class^="highlight"] pre, 523 | .rst-content pre.literal-block { 524 | font-size: 87%; 525 | } 526 | 527 | table.highlighttable { 528 | padding-left: 0; 529 | } 530 | table.highlighttable .highlight { 531 | padding-left: 0; 532 | margin: 0; 533 | border: none; 534 | } 535 | .highlight > pre { 536 | margin-bottom: 0; 537 | } 538 | .highlight pre span { 539 | font-weight: normal !important; 540 | } 541 | .highlight .hll { 542 | display: block; 543 | width: 100%; 544 | } 545 | .highlighttable .linenos { 546 | background-color: #f3f2f1; 547 | padding-left: 15px; 548 | padding-right: 15px; 549 | padding-top: 15px; 550 | } 551 | .highlighttable .code { 552 | width: 100%; 553 | } 554 | 555 | .wy-nav-content footer { 556 | padding: 25px 50px; 557 | margin-right: -50px; 558 | margin-left: -50px; 559 | border-top: 1px solid #e1dfdd; 560 | background-color: #f3f2f1; 561 | color: #797775 562 | } 563 | footer p { 564 | font-size: 85%; 565 | } 566 | 567 | .wy-breadcrumbs + hr, 568 | footer hr { 569 | margin-left: -50px; 570 | margin-right: -50px; 571 | } 572 | 573 | @media screen and (max-width:768px) { 574 | .wy-nav-content-wrap .wy-nav-content { 575 | padding: 25px; 576 | padding-bottom: 0; 577 | border: none; 578 | } 579 | 580 | div.admonition { 581 | margin-left: -25px; 582 | margin-right: -25px; 583 | padding: 25px 25px !important; 584 | } 585 | 586 | .headerlink { 587 | visibility: visible !important; 588 | } 589 | 590 | .rst-content div[class^="highlight"], 591 | .rst-content pre.literal-block { 592 | margin-left: -25px; 593 | margin-right: -25px; 594 | padding: 25px 25px; 595 | } 596 | 597 | .wy-nav-content footer { 598 | padding: 25px 25px; 599 | margin-right: -25px; 600 | margin-left: -25px; 601 | } 602 | 603 | .wy-breadcrumbs + hr, 604 | footer hr { 605 | margin-left: -25px; 606 | margin-right: -25px; 607 | } 608 | } 609 | 610 | /* 611 | * Pygments code highlight theme 612 | */ 613 | 614 | .highlight, .highlighttable .code { 615 | background-color: #f7f7f9; 616 | } 617 | 618 | /* Highlited line */ 619 | .highlight .hll { 620 | background-color: #edebe9; 621 | } 622 | 623 | /* Comments */ 624 | .highlight .c, 625 | .highlight .cm, 626 | .highlight .cp, 627 | .highlight .c1, 628 | .highlight .cs { 629 | color: #999; 630 | } 631 | 632 | /* Strings */ 633 | .highlight .s, 634 | .highlight .sb, 635 | .highlight .sc, 636 | .highlight .sd, 637 | .highlight .s2, 638 | .highlight .se, 639 | .highlight .sh, 640 | .highlight .si, 641 | .highlight .sx, 642 | .highlight .sr, 643 | .highlight .s1, 644 | .highlight .ss { 645 | color: #489741; 646 | } 647 | 648 | /* Keywords */ 649 | .highlight .k, 650 | .highlight .kn, 651 | .highlight .kd { 652 | color: #df115e; 653 | } 654 | 655 | /* Class name, functions, methods, builtins, imported module */ 656 | .highlight .nn, 657 | .highlight .nf, 658 | .highlight .na, 659 | .highlight .nt, 660 | .highlight .fm, 661 | .highlight .nc { 662 | color: #0e84b5; 663 | } 664 | 665 | /* self */ 666 | .highlight .bp { 667 | color: #007020; 668 | } 669 | 670 | .highlight .ow { 671 | color: #d96600; 672 | } 673 | -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhch/django-jsonform/e485ef149902962a5b9ec83bcb1150ffbee64ef6/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/_static/file-upload.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhch/django-jsonform/e485ef149902962a5b9ec83bcb1150ffbee64ef6/docs/_static/file-upload.gif -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhch/django-jsonform/e485ef149902962a5b9ec83bcb1150ffbee64ef6/docs/_static/logo.png -------------------------------------------------------------------------------- /docs/_static/quickstart.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhch/django-jsonform/e485ef149902962a5b9ec83bcb1150ffbee64ef6/docs/_static/quickstart.gif -------------------------------------------------------------------------------- /docs/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends 'sphinx_rtd_theme/layout.html' %} 2 | 3 | {%- block extrahead %} 4 | 5 | 6 | 7 | {%- if google_search_console_verification %} 8 | 9 | {% endif -%} 10 | {% endblock %} -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('..')) 16 | sys.path.append(os.path.abspath("./_ext")) 17 | 18 | from django_jsonform import __version__ 19 | 20 | 21 | # -- Project information ----------------------------------------------------- 22 | 23 | project = 'django-jsonform' 24 | copyright = '2021 Bharat Chauhan' 25 | author = 'Bharat Chauhan' 26 | 27 | version = __version__ 28 | 29 | extensions = [ 30 | 'sphinx.ext.autosectionlabel', 31 | 'sphinx.ext.extlinks', 32 | 'helpers', 33 | ] 34 | 35 | add_module_names = False 36 | 37 | templates_path = ['_templates'] 38 | 39 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 40 | 41 | html_theme = 'sphinx_rtd_theme' 42 | html_static_path = ['_static'] 43 | html_theme_options = { 44 | 'logo_only': True, 45 | } 46 | html_logo = '_static/logo.png' 47 | html_favicon = '_static/favicon.ico' 48 | html_css_files = [ 49 | 'candy.css', 50 | # We load fonts directly in `layout.html` template because 51 | # specifying links here doesn't not allow for setting 52 | # "preconnect" and "crossorigin" attributes on the tags. 53 | ] 54 | html_show_sphinx = False 55 | html_context = { 56 | 'google_search_console_verification': '8Isauv2ltEzkMMhQeMjQedq39VQFeKa0sZXqnPEHpFg' 57 | } 58 | 59 | extlinks = { 60 | 'issue': ( 61 | 'https://github.com/bhch/django-jsonform/issues/%s', 62 | '#' # verions 4+ also require '%s' in this value 63 | ), 64 | 'pr': ( 65 | 'https://github.com/bhch/django-jsonform/pull/%s', 66 | '#' # verions 4+ also require '%s' in this value 67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | Here are some schema examples to achieve fairly complex data structures. 5 | 6 | .. seealso:: 7 | `Live Demos & Playground `__ 8 | 9 | Menu 10 | ---- 11 | 12 | This example shows how you can use JSON to store menu items for your website. 13 | 14 | First let's look at the expected data structure: 15 | 16 | .. code-block:: javascript 17 | 18 | // Data (javascript/json) 19 | [ 20 | {'label': 'Home', 'link': '/', 'new_tab': false}, 21 | {'label': 'About', 'link': '/about/', 'new_tab': false}, 22 | {'label': 'Twitter', 'link': 'https://twitter.com/', 'new_tab': true}, 23 | ] 24 | 25 | 26 | And here's the corresponding schema for obtaining the above data: 27 | 28 | 29 | .. code-block:: python 30 | 31 | # Schema 32 | { 33 | 'type': 'list', 34 | 'items': { 35 | 'type': 'dict', 36 | 'keys': { 37 | 'label': { 38 | 'type': 'string' 39 | }, 40 | 'link': { 41 | 'type': 'string' 42 | }, 43 | 'new_tab': { 44 | 'type': 'boolean', 45 | 'title': 'Open in new tab' 46 | } 47 | } 48 | } 49 | } 50 | 51 | 52 | Menu with nested items 53 | ---------------------- 54 | 55 | A menu item can either be a link or a dropdown containing multiple links as its children. 56 | 57 | You can recursively nest an object within itself using the ``$ref`` keyword. See docs 58 | on :ref:`referencing schema` for details: 59 | 60 | .. code-block:: python 61 | :emphasize-lines: 17, 18, 19 62 | 63 | # Schema 64 | { 65 | 'type': 'list', 66 | 'items': { 67 | 'type': 'dict', 68 | 'keys': { 69 | 'label': { 70 | 'type': 'string' 71 | }, 72 | 'link': { 73 | 'type': 'string' 74 | }, 75 | 'new_tab': { 76 | 'type': 'boolean', 77 | 'title': 'Open in new tab' 78 | }, 79 | 'children': { 80 | '$ref': '#' 81 | } 82 | } 83 | } 84 | } 85 | 86 | 87 | Image slider 88 | ------------ 89 | 90 | This example shows you how you can store an image slider in JSON. 91 | 92 | First, let's look at the expected data structure: 93 | 94 | .. code-block:: javascript 95 | 96 | // Data (javascript/json) 97 | [ 98 | { 99 | 'image': 'images/slide-1.png', 100 | 'heading': 'This is slide 1', 101 | 'caption': 'This is a caption', 102 | 'button': { 103 | 'label': 'Sign up', 104 | 'link': '/sign-up/' 105 | } 106 | }, 107 | { 108 | 'image': 'images/slide-2.png', 109 | 'heading': 'This is slide 2', 110 | 'caption': 'This is another caption', 111 | 'button': { 112 | 'label': 'Learn more', 113 | 'link': '/learn-more/' 114 | } 115 | } 116 | ] 117 | 118 | 119 | And here's the corresponding schema for obtaining the above data: 120 | 121 | 122 | .. code-block:: python 123 | 124 | # Schema 125 | { 126 | 'type': 'list', 127 | 'items': { 128 | 'type': 'dict', 129 | 'keys': { 130 | 'image': { 131 | 'type': 'string', 132 | 'format': 'file-url' 133 | }, 134 | 'heading': { 135 | 'type': 'string' 136 | }, 137 | 'caption': { 138 | 'type': 'string' 139 | }, 140 | 'button': { 141 | 'type': 'object', 142 | 'keys': { 143 | 'label': { 144 | 'type': 'string' 145 | }, 146 | 'link': { 147 | 'type': 'string' 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /docs/fields-and-widgets.rst: -------------------------------------------------------------------------------- 1 | Fields and Widgets 2 | ================== 3 | 4 | .. note:: 5 | This document is for model fields and widgets. 6 | If you're looking for HTML form input fields, see: 7 | :doc:`User's guide on Input field types `. 8 | 9 | Contents: 10 | 11 | .. contents:: 12 | :depth: 2 13 | :local: 14 | :backlinks: none 15 | 16 | Model fields 17 | ------------ 18 | 19 | .. module:: django_jsonform.models.fields 20 | :synopsis: Model fields 21 | 22 | 23 | ``JSONField`` 24 | ~~~~~~~~~~~~~ 25 | 26 | .. class:: JSONField(schema=None, pre_save_hook=None, file_handler=None, **options) 27 | 28 | .. versionadded:: 2.0 29 | 30 | It is basically a subclass of Django's ``JSONField``, but for convenience, 31 | it automatically sets up the JSON editor widget for you. 32 | 33 | 34 | In Django < 3.1, for databases other than Postgres, it uses a ``TextField`` 35 | underneath. 36 | 37 | **Parameters**: 38 | 39 | .. attribute:: schema 40 | :type: dict, callable 41 | 42 | A ``dict`` or a callable object specifying the schema for the current field. 43 | 44 | A callable is useful for :ref:`specifying dynamic choices `. 45 | 46 | The callable function may optionally receive the current model instance. See: 47 | :ref:`Accessing model instance in callable schema`. 48 | 49 | .. versionchanged:: 2.1 50 | The ability to provide a callable was added. 51 | 52 | .. versionchanged:: 2.8 53 | Callable schema may receive an ``instance`` argument. 54 | 55 | .. attribute:: pre_save_hook 56 | :type: callable 57 | 58 | .. versionadded:: 2.10 59 | 60 | (Optional) Sometimes you may wish to transform the JSON data before saving 61 | in the database. 62 | 63 | For that purpose, you can provide a callable through this argument which 64 | will be called before saving the field's value in the database. 65 | 66 | The ``pre_save_hook`` callable will receive the current value of the field 67 | as the only argument. It must return the value which you intend to save in 68 | the database. 69 | 70 | .. code-block:: 71 | 72 | def pre_save_hook(value): 73 | # do something with the value ... 74 | return value 75 | 76 | 77 | class MyModel(...): 78 | items = JSONField(schema=..., pre_save_hook=pre_save_hook) 79 | 80 | .. attribute:: file_handler 81 | :type: str 82 | 83 | .. versionadded:: 2.11 84 | 85 | (Optional) Provide a the url of the view for handling file uploads. See :ref:`document 86 | on uploading files ` for usage. 87 | 88 | .. attribute:: **options 89 | 90 | This ``JSONField`` accepts all the arguments accepted by Django's 91 | ``JSONField``, such as a custom ``encoder`` or ``decoder``. 92 | 93 | For details about other parameters, options and attributes of the 94 | ``JSONField``, see `Django's docs 95 | `__. 96 | 97 | Usage: 98 | 99 | .. code-block:: python 100 | 101 | from django_jsonform.models.fields import JSONField 102 | 103 | 104 | class MyModel(models.Model): 105 | ITEMS_SCHEMA = {...} 106 | 107 | items = JSONField(schema=ITEMS_SCHEMA) 108 | 109 | 110 | ``ArrayField`` 111 | ~~~~~~~~~~~~~~ 112 | 113 | .. class:: ArrayField(base_field, size=None, **options) 114 | 115 | .. versionadded:: 2.0 116 | 117 | A subclass of Django's ``ArrayField`` except it renders a dynamic form widget. 118 | 119 | It takes exactly the same arguments as the original class. 120 | 121 | It also supports multiple levels of array nesting. 122 | 123 | Usage: 124 | 125 | .. code-block:: python 126 | 127 | from django_jsonform.models.fields import ArrayField 128 | 129 | 130 | class MyModel(models.Model): 131 | items = ArrayField(models.CharField(max_length=50), size=10) 132 | # ... 133 | 134 | For more details, see 135 | `Django's docs `__. 136 | 137 | 138 | Form fields 139 | ----------- 140 | 141 | .. module:: django_jsonform.forms.fields 142 | :synopsis: Form fields 143 | 144 | .. _form-jsonfield: 145 | 146 | ``JSONFormField`` 147 | ~~~~~~~~~~~~~~~~~ 148 | 149 | .. class:: JSONFormField(schema=None, model_name='', field_name='', **options) 150 | 151 | .. versionadded:: 2.0 152 | 153 | It is a subclass of Django's ``forms.JSONField``. 154 | 155 | **Parameters**: 156 | 157 | .. attribute:: schema 158 | :type: dict, callable 159 | 160 | A ``dict`` or a callable object. 161 | 162 | .. attribute:: model_name 163 | :type: string 164 | 165 | (Optional) Name of the model. It is sent along with the AJAX requests to your file handler 166 | view. 167 | 168 | .. attribute:: file_handler 169 | :type: string 170 | 171 | (Optional) Provide a the url of the view for handling file uploads. 172 | 173 | .. attribute:: **options 174 | 175 | It also accepts other options which are accepted by Django's ``forms.JSONField``. 176 | 177 | For details about other parameters, options and attributes, see `Django's docs 178 | `__. 179 | 180 | Usage: 181 | 182 | .. code-block:: python 183 | 184 | from django_jsonform.forms.fields import JSONFormField 185 | 186 | class MyForm(forms.Form): 187 | my_field = JSONFormField(schema=schema) 188 | 189 | Widgets 190 | ------- 191 | 192 | .. module:: django_jsonform.widgets 193 | :synopsis: Widgets 194 | 195 | 196 | ``JSONFormWidget`` 197 | ~~~~~~~~~~~~~~~~~~ 198 | 199 | .. class:: JSONFormWidget(schema, model_name='', file_handler='', validate_on_submit=False, attrs=None) 200 | 201 | The widget which renders the editor. 202 | 203 | It can be used in a form if you don't want to use the model field. 204 | 205 | **Parameters**: 206 | 207 | .. attribute:: schema 208 | :type: dict, callable 209 | 210 | A ``dict`` or a callable object specifying the schema for the current field. 211 | 212 | A callable is useful for :ref:`specifying dynamic choices `. 213 | 214 | The callable function may optionally receive the current model instance. 215 | See: :ref:`Accessing model instance in callable schema`. 216 | 217 | .. versionchanged:: 2.1 218 | The ability to provide a callable was added. 219 | 220 | .. versionchanged:: 2.8 221 | Callable schema may receive an ``instance`` argument. 222 | 223 | .. attribute:: model_name 224 | :type: str 225 | 226 | (Optional). The name of the model. It is passed to the file upload handler 227 | so that you can identify which model is requesting the file upload. 228 | 229 | See :ref:`Handling file uploads` for more details. 230 | 231 | .. attribute:: file_handler 232 | :type: str 233 | 234 | .. versionadded:: 2.11 235 | 236 | (Optional) Provide a the url of the view for handling file uploads. See :ref:`document 237 | on uploading files ` for usage. 238 | 239 | .. attribute:: validate_on_submit 240 | :type: bool 241 | 242 | .. versionadded:: 2.12 243 | 244 | (Optional) Whether to validate the data on the browser when form is submitted. 245 | 246 | Default ``False``. 247 | 248 | See :ref:`Validating data in browser ` for more. 249 | 250 | .. attribute:: attrs 251 | :type: dict 252 | 253 | .. versionadded:: 2.12 254 | 255 | (Optional) A dictionary mapping of HTML attributes and values for the widget 256 | container element. 257 | 258 | Usage: 259 | 260 | .. code-block:: python 261 | 262 | # admin.py 263 | 264 | from django_jsonform.widgets import JSONFormWidget 265 | from myapp.models import ShoppingList 266 | 267 | 268 | class ShoppingListForm(forms.ModelForm): 269 | class Meta: 270 | model = ShoppingList 271 | fields = '__all__' 272 | widgets = { 273 | 'items': JSONFormWidget(schema=ShoppingList.ITEMS_SCHEMA) 274 | } 275 | 276 | class ShoppingListAdmin(admin.ModelAdmin): 277 | form = ShoppingListForm 278 | 279 | admin.site.register(ShoppingList, ShoppingListAdmin) 280 | 281 | 282 | This widget can not be used directly with Django's ``ArrayField`` because 283 | Django's ``ArrayField`` converts the value from array to a string before passing 284 | it to the widget whereas it expects a list or a dict. 285 | 286 | 287 | Accessing model instance in callable schema 288 | ------------------------------------------- 289 | 290 | .. versionadded:: 2.8 291 | 292 | Automatically accessing model instance in a widget is not possible. This is due 293 | the way Django initialises the widgets and form fields. 294 | 295 | However, you can bypass this limitation by manually setting an ``instance`` 296 | attribute on the widget. 297 | 298 | To do this, you are required to create a custom form class for your model: 299 | 300 | .. code-block:: python 301 | 302 | # models.py 303 | 304 | def callable_schema(instance=None): 305 | # instance will be None while creating new object 306 | 307 | if instance: 308 | # ... do something with the instance ... 309 | else: 310 | # ... do something else ... 311 | return schema 312 | 313 | 314 | class MyModel(models.Model): 315 | my_field = JSONField(schema=callable_schema) 316 | 317 | 318 | ... 319 | 320 | # admin.py 321 | 322 | # create a custom modelform 323 | class MyModelForm(forms.ModelForm): 324 | def __init__(self, *args, **kwargs): 325 | super().__init__(*args, **kwargs) 326 | # manually set the current instance on the widget 327 | self.fields['my_field'].widget.instance = self.instance 328 | 329 | 330 | # set the form on the admin class 331 | class MyAdmin(admin.ModelAdmin): 332 | form = MyModelForm 333 | 334 | 335 | admin.site.register(MyModel, MyAdmin) 336 | 337 | 338 | Your callable schema function will get the current model ``instance`` on *Edit/Change* 339 | admin page. It will be ``None`` on the *Add new* page (*i.e.* while creating new objects). 340 | 341 | 342 | Making the whole JSON form readonly 343 | ----------------------------------- 344 | 345 | .. versionadded:: 2.19 346 | 347 | It is possible to make the whole JSON form readonly dynamically on a per user or 348 | per request basis. 349 | 350 | This can be done by setting the ``disabled`` attribute on the JSON form field. 351 | Note that this attribute is set on the form field itself, not on the field's widget. 352 | 353 | You are also required to create a custom form class for your model: 354 | 355 | .. code-block:: python 356 | :emphasize-lines: 8 357 | 358 | # admin.py 359 | 360 | # create a custom modelform 361 | class MyModelForm(forms.ModelForm): 362 | def __init__(self, *args, **kwargs): 363 | super().__init__(*args, **kwargs) 364 | 365 | self.fields['my_json_field'].disabled = True # disable the field 366 | 367 | 368 | # set the form on the admin class 369 | class MyAdmin(admin.ModelAdmin): 370 | form = MyModelForm 371 | 372 | 373 | admin.site.register(MyModel, MyAdmin) 374 | 375 | 376 | Now the whole form will be rendered in readonly mode. 377 | 378 | Security wise this works just as well because the ``disabled`` attribute on a form field tells 379 | Django to ignore that field's value on form submission. See also: `Django docs on Field.disabled `__. 380 | -------------------------------------------------------------------------------- /docs/guide/arrayfield.rst: -------------------------------------------------------------------------------- 1 | Using with Postgres ``ArrayField`` 2 | ================================== 3 | 4 | django-jsonform provides a custom :class:`~django_jsonform.models.fields.ArrayField` 5 | class which renders a dynamic form input. 6 | 7 | It is a subclass of Django's ``ArrayField`` and the usage api is exactly the same. 8 | 9 | .. code-block:: python 10 | 11 | from django_jsonform.models.fields import ArrayField 12 | 13 | 14 | class MyModel(models.Model): 15 | items = ArrayField(models.CharField(max_length=50), size=10) 16 | 17 | 18 | .. _arrayfield custom schema: 19 | 20 | Custom schema for ``ArrayField`` 21 | -------------------------------- 22 | 23 | .. versionadded:: 2.23 24 | 25 | Overriding the schema is useful when you want to render custom widgets on the 26 | browser. 27 | 28 | However, it is your responsibility to write the correct schema as mismatches in 29 | data structure may cause validation errors while saving data. 30 | 31 | Example: 32 | 33 | .. code:: python 34 | 35 | from django_jsonform.models.fields import ArrayField 36 | 37 | 38 | class MyModel(...): 39 | ITEMS_SCHEMA = { 40 | "type": "array", 41 | "items": { 42 | "type": "string", 43 | "maxLength": 50 44 | }, 45 | "maxItems": 10 46 | } 47 | 48 | items = ArrayField(models.CharField(max_length=50), size=10, schema=ITEMS_SCHEMA) 49 | 50 | 51 | .. _arrayfield custom widget: 52 | 53 | Overriding the widget for ``ArrayField`` 54 | ---------------------------------------- 55 | 56 | .. versionadded:: 2.23 57 | 58 | The default widget for the ``ArrayField`` can be overridden to provide custom schema 59 | and do some other stuff. 60 | 61 | If you only need to provide custom schema, then it's better to just use the ``schema`` 62 | argument as described in the :ref:`previous section `. 63 | 64 | Overriding the widget for ``ArrayField`` works the same way as for the ``JSONField``. 65 | For usage examples, see: :class:`~django_jsonform.widgets.JSONFormWidget`. 66 | -------------------------------------------------------------------------------- /docs/guide/autocomplete.rst: -------------------------------------------------------------------------------- 1 | Autocomplete widget 2 | =================== 3 | 4 | .. versionadded:: 2.12 5 | 6 | The autocomplete widget can be used for fetching options from the server via AJAX 7 | requests. 8 | 9 | **Here's an animated GIF of the autocomplete widget:** 10 | 11 | .. image:: /_static/autocomplete.gif 12 | :alt: Animated screenshot of autocomplete widget. 13 | 14 | Usage: 15 | 16 | .. code-block:: python 17 | :emphasize-lines: 5,6 18 | 19 | # Schema 20 | 21 | { 22 | 'type': 'string', 23 | 'widget': 'autocomplete', 24 | 'handler': '/url/to/handler_view' # hard-coded url 25 | 26 | # OR 27 | 28 | 'handler': reverse_lazy('handler-view-name') # reversed URL 29 | } 30 | 31 | 32 | 33 | The ``handler`` keyword declares the URL of the view which will handle the AJAX 34 | requests. 35 | 36 | You can use ``django.urls.reverse_lazy`` instead of hard-coding the handler url. 37 | 38 | 39 | Multiselect + Autocomplete 40 | -------------------------- 41 | 42 | .. vsersionadded:: 2.20 43 | 44 | You can use ``"widget": "multiselect-autocomplete"`` to get an autocomplete input 45 | with multiple selections. 46 | 47 | 48 | Handling AJAX requests 49 | ---------------------- 50 | 51 | Your handler view will receive a ``GET`` request with a ``query`` parameter 52 | containing the search term typed in the autocomplete input. 53 | 54 | Code example 55 | ~~~~~~~~~~~~ 56 | 57 | .. code-block:: python 58 | 59 | from django.http import JsonResponse 60 | from django.contrib.auth.decorators import login_required 61 | 62 | @login_required 63 | def autocomplete_handler(request): 64 | query = request.GET.get('query') # search query 65 | 66 | # ... do something ... 67 | 68 | results = [ 69 | 'Australia', 70 | 'Brazil', 71 | ] 72 | 73 | return JsonResponse({'results': results}) 74 | 75 | 76 | Request arguments 77 | ~~~~~~~~~~~~~~~~~ 78 | 79 | Each ``GET`` request will contain these parameters: 80 | 81 | - ``query``: Search term typed in the autocomplete input. 82 | - ``model_name``: Name of the model. 83 | - ``field_name``: Name of the field. 84 | - ``coords``: :doc:`Coordinates ` of the data input field. 85 | 86 | Response format 87 | ~~~~~~~~~~~~~~~ 88 | 89 | Your view must return a ``JsonResponse`` in this format: 90 | 91 | .. code-block:: python 92 | 93 | JsonResponse({'results': ['Australia', 'Brazil', ...]}) 94 | 95 | The options can also have different display label and value: 96 | 97 | .. code-block:: python 98 | 99 | JsonResponse({ 100 | 'results': [ 101 | {'title': 'Australia', 'value': 'AU'}, 102 | {'title': 'Brazil', 'value': 'BR'}, 103 | ... 104 | ] 105 | }) 106 | -------------------------------------------------------------------------------- /docs/guide/choices.rst: -------------------------------------------------------------------------------- 1 | Choices 2 | ======= 3 | 4 | You can specify choices for an input field using the ``choices`` (or ``enum``) keyword. 5 | 6 | Choices can be specified for any type of input - ``string``, ``number``, ``boolean`` etc. 7 | 8 | .. versionchanged:: 2.11.0 9 | Support for ``enum`` keyword was added. 10 | 11 | .. versionchanged:: 2.12.0 12 | ``title`` keyword was added as an alias for the ``label`` keyword. 13 | 14 | Specifying choices 15 | ------------------ 16 | 17 | .. code-block:: python 18 | 19 | { 20 | 'type': 'string', 21 | 'choices': ['Eggs', 'Milk', 'Juice'] # you can also use 'enum' keyword 22 | } 23 | 24 | 25 | You can also specify a different title for displaying to the user while the 26 | underlying value is different. 27 | 28 | .. code-block:: python 29 | 30 | { 31 | 'type': 'string', 32 | 'choices': [ 33 | {'title': 'New York', 'value': 'NY'}, 34 | {'title': 'California', 'value': 'CA'}, 35 | {'title': 'Texas', 'value': 'TX'}, 36 | ] 37 | } 38 | 39 | 40 | Customizing the input field 41 | --------------------------- 42 | 43 | By default, a ``select`` input is rendered for the choices. 44 | 45 | You can also use a ``radio`` input using the ``widget`` keyword: 46 | 47 | .. code-block:: python 48 | 49 | { 50 | 'type': 'string', 51 | 'choices': ['Eggs', 'Milk', 'Juice'], 52 | 'widget': 'radio' 53 | } 54 | 55 | 56 | Multiple selections 57 | ------------------- 58 | 59 | .. versionadded:: 2.8 60 | 61 | For multiple selections, you'll have to use an ``array`` type to hold the selected 62 | values. 63 | 64 | To disallow users from selecting the same value multiple times, you can use ``multiselect`` widget. 65 | 66 | .. code-block:: python 67 | 68 | { 69 | 'type': 'array', 70 | 'items': { 71 | 'type': 'string', 72 | 'choices': ['Eggs', 'Milk', 'Juice'], 73 | 'widget': 'multiselect' 74 | } 75 | } 76 | 77 | The ``multiselect`` widget ensures that one value can only be selected once. 78 | 79 | Don't use ``multiselect`` widget if you want to let your users select the same value 80 | multiple times. 81 | 82 | 83 | Dynamic choices 84 | --------------- 85 | 86 | In some cases, you might want to return choices dynamically, such as by reading 87 | objects from the database. 88 | 89 | For that purpose, the ``schema`` can be a callable object: 90 | 91 | .. code-block:: python 92 | 93 | def dynamic_schema(): 94 | # here, you can create a schema dynamically 95 | # such as read data from database and populate choices 96 | schema = {...} 97 | return schema 98 | 99 | 100 | class MyModel(models.Model): 101 | items = JSONField(schema=dynamic_schema) 102 | 103 | 104 | AJAX choices 105 | ------------ 106 | 107 | See :doc:`Autocomplete widget ` for loading choices via AJAX 108 | requests. 109 | -------------------------------------------------------------------------------- /docs/guide/coordinates.rst: -------------------------------------------------------------------------------- 1 | Coordinates 2 | =========== 3 | 4 | *"Coordinates"* are a way to locate a nested item in a data. 5 | 6 | Consider the following data—a list of persons: 7 | 8 | .. code-block:: python 9 | 10 | data = [ 11 | { 12 | 'name': 'Alice', 13 | 'age': 30, 14 | 'children':[ 15 | { 16 | 'name': 'Carl', 17 | 'age': 8 18 | } 19 | ] 20 | }, 21 | 22 | { 23 | 'name': 'Bob', 24 | 'age': '40', 25 | 'children': [] 26 | } 27 | ] 28 | 29 | To access the ``age`` of the **first person** (i.e. Alice) in the list, you do this: 30 | 31 | .. code-block:: python 32 | 33 | data[0]['age'] # data > first item > age 34 | 35 | 36 | *Coordinates* is basically a string containing the chain of the keys and indices 37 | to locate a field in the data. 38 | 39 | .. note:: 40 | 41 | To join the coordinates into a string, we use a special symbol called the **section sign** (``§``). 42 | Earlier, we used the hyphen (``-``) as the separator, but that made it impossible for 43 | the schema object keys (i.e. field names) to contain the hyphen. 44 | 45 | So, for above example, the coordinates string is: ``'0§age'``. We've just chained the 46 | index and key into a string. 47 | 48 | To access the ``age`` of the **first child** (i.e. Carl) of **first person** (i.e. Alice) 49 | in the list, you perform this lookup: 50 | 51 | .. code-block:: python 52 | 53 | data[0]['children'][0]['age'] # data > first item > children > first item > age 54 | 55 | This time, the coordinates string is: ``'0§children§0§age'``. 56 | 57 | 58 | To make it easier to generate the coordinates string and to avoid copying and pasting 59 | the section symbol, you can use the :func:`~django_jsonform.utils.join_coords` helper function: 60 | 61 | .. code-block:: 62 | 63 | from django_jsonform.utils import join_coords 64 | 65 | join_coords(0, 'age') # -> '0§age' 66 | 67 | 68 | Uses of coordinates 69 | ------------------- 70 | 71 | 1. Displaying error messages 72 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 73 | 74 | Coordinates are used by the library for displaying error messages for a field. 75 | 76 | To display error message under the age field of first person, you can do this: 77 | 78 | .. code-block:: python 79 | 80 | error_map = { 81 | '0§age': 'Invalid value', 82 | } 83 | 84 | The library will use the coordinates to display an error message under the appropriate field. 85 | 86 | Alternatively, instead of manually copying this symbol, you can use the 87 | :class:`~django_jsonform.utils.ErrorMap` helper class to create the error map. 88 | 89 | 2. In handler views 90 | ~~~~~~~~~~~~~~~~~~~ 91 | 92 | You can use the coordinates in a file handler view or autocomplete handler view 93 | to determine the field in the schema which sent the request. 94 | 95 | This is useful if you have one common handler for multiple schemas and you want to 96 | return different response based on the schema field. 97 | 98 | To split coordinates string into individual coordinates, use the 99 | :func:`~django_jsonform.utils.split_coords` helper function: 100 | 101 | .. code-block:: 102 | 103 | from django_jsonform.utils import split_coords 104 | 105 | split_coords('0§age') # -> ['0', 'age'] 106 | -------------------------------------------------------------------------------- /docs/guide/index.rst: -------------------------------------------------------------------------------- 1 | User's guide 2 | ============ 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | inputs 8 | choices 9 | upload 10 | autocomplete 11 | javascript 12 | arrayfield 13 | translations 14 | validation 15 | coordinates -------------------------------------------------------------------------------- /docs/guide/inputs.rst: -------------------------------------------------------------------------------- 1 | Input field types 2 | ================= 3 | 4 | There are four values for the ``type`` keyword which have an input field: 5 | 6 | 1. ``string`` - For text, email, date, file, and other inputs. 7 | 2. ``number`` - For number input (including floats). 8 | 3. ``integer`` - For integer only number input. 9 | 4. ``boolean`` - For ``True`` - ``False`` inputs (checkbox by default). 10 | 11 | We've excluded ``array`` and ``object`` types as they can't have input fields. 12 | 13 | 14 | .. _inputs for string type: 15 | 16 | Inputs for ``string`` type 17 | -------------------------- 18 | 19 | The input fields for ``string`` values can be customized using the ``format`` 20 | and ``widget`` keywords. 21 | 22 | Available ``format`` values for ``string`` type: 23 | 24 | ================= =========== 25 | Format Description 26 | ================= =========== 27 | ``color`` A colour input 28 | ``date`` A date input 29 | ``date-time`` A datetime input. See :ref:`Datetime field` for details. 30 | ``datetime`` Alias for ``date-time`` 31 | ``email`` An email input 32 | ``password`` A password input 33 | ``time`` A time input 34 | ``data-url`` A file input. See :doc:`Uploading files ` for usage. 35 | ``file-url`` A file input. See :doc:`Uploading files ` for usage. 36 | ``uri`` A URL input for absolute links only. 37 | ``uri-reference`` A URL input for absolute as well as relative links. 38 | ================= =========== 39 | 40 | Available ``widget`` values for ``string`` type: 41 | 42 | ================ =========== 43 | Widget Description 44 | ================ =========== 45 | ``textarea`` A textarea input 46 | ``radio`` A radio input (:doc:`useful for choices `) 47 | ``autocomplete`` Useful for fetching options via AJAX requests (:doc:`See usage `) 48 | ``fileinput`` A file input. Useful for overriding ``file-url`` input (:ref:`See usage `) 49 | ``hidden`` A hidden input 50 | ================ =========== 51 | 52 | 53 | Examples: 54 | 55 | .. code-block:: python 56 | 57 | # 1. Text input (default) 58 | { 59 | 'type': 'string' 60 | } 61 | 62 | 63 | # 2. Date input 64 | { 65 | 'type': 'string', 66 | 'format': 'date' 67 | } 68 | 69 | 70 | # 3. Email input 71 | { 72 | 'type': 'string', 73 | 'format': 'email' 74 | } 75 | 76 | # 4. Textarea input 77 | { 78 | 'type': 'string', 79 | 'widget': 'textarea' 80 | } 81 | 82 | # ... 83 | 84 | 85 | Inputs for ``number`` and ``integer`` types 86 | ------------------------------------------- 87 | 88 | The ``number`` and ``integer`` types get an HTML ``number`` input field by default. 89 | These can be customized using the ``widget`` keyword. 90 | 91 | Available ``widget`` values for ``number`` & ``integer`` type: 92 | 93 | ================ =========== 94 | Widget Description 95 | ================ =========== 96 | ``range`` A range HTML input. 97 | ================ =========== 98 | 99 | 100 | Inputs for ``boolean`` type 101 | --------------------------- 102 | 103 | The ``boolean`` type gets an HTML ``checkbox`` input. Currently, it can't be 104 | customized to another input type. 105 | 106 | However, you can use :doc:`choices ` to display a ``radio`` or ``select`` 107 | input with *Yes/No* options to choose from. 108 | 109 | 110 | Input for ``const`` keyword 111 | --------------------------- 112 | 113 | .. versionadded:: 2.20 114 | 115 | The ``const`` keyword gets a readonly input. It can also be hidden by using a ``hidden`` widget. 116 | 117 | 118 | Default values 119 | -------------- 120 | 121 | .. versionadded:: 2.6 122 | 123 | You can specify default initial values for inputs using the ``default`` keyword: 124 | 125 | .. code-block:: python 126 | 127 | # 1. String input 128 | { 129 | 'type': 'string', 130 | 'default': 'Hello world' 131 | } 132 | 133 | # 2. Boolean 134 | { 135 | 'type': 'boolean', 136 | 'default': True 137 | } 138 | 139 | # 3. Default choice 140 | { 141 | 'type': 'string', 142 | 'choices': ['Eggs', 'Juice', 'Milk'], 143 | 'default': 'Milk' 144 | } 145 | 146 | # 4. Default array items 147 | { 148 | 'type': 'array', 149 | 'items': { 150 | 'type': 'string', 151 | 'default': 'Hello world' # default value for every new array item 152 | } 153 | } 154 | 155 | 156 | Readonly inputs 157 | --------------- 158 | 159 | .. versionadded:: 2.6 160 | 161 | You can make inputs uneditable using a ``readonly`` (alias ``readOnly``) keyword: 162 | 163 | .. code-block:: python 164 | 165 | # 1. String inputs 166 | { 167 | 'type': 'string', 168 | 'readonly': True 169 | } 170 | 171 | # 2. Array items 172 | { 173 | 'type': 'array', 174 | 'items': { 175 | 'type': 'string', 176 | 'readonly': True # all items will be readonly 177 | } 178 | } 179 | 180 | .. seealso:: 181 | 182 | To make the whole form readonly instead of individual fields, see: 183 | :ref:`Making the whole JSON form readonly`. 184 | 185 | 186 | Multiselect inputs 187 | ------------------ 188 | 189 | Multiselect inputs are only supported on ``array`` type. 190 | 191 | 192 | Available multiselect ``widgets``: 193 | 194 | ============================ =========== 195 | Widget Description 196 | ============================ =========== 197 | ``multiselect`` A multiselect drowpdown input (:ref:`useful for choices `) 198 | ``multiselect-autocomplete`` A multiselect autocomplete input (:doc:`See usage `) 199 | ============================ =========== 200 | 201 | 202 | 203 | Datetime field 204 | -------------- 205 | 206 | .. versionadded:: 2.8 207 | 208 | Usage: 209 | 210 | .. code-block:: python 211 | 212 | { 213 | 'type': 'string', 214 | 'format': 'datetime' # or 'date-time' 215 | } 216 | 217 | The value will be saved as ISO formatted date, such as: ``2022-02-06T15:42:11.000+00:00``. 218 | 219 | Timezone conversion 220 | ~~~~~~~~~~~~~~~~~~~ 221 | 222 | When a user selects the time on their browser, it will be interpreted in their 223 | operating system's local timezone. Then, the widget will convert it to UTC for 224 | saving in the database. 225 | 226 | Also, the widget's time picker is in 12-hour format, but the final value will be 227 | converted to 24-hour format. 228 | 229 | Example: Suppose there's a user whose timezone is +5:30 (Indian Standard Time). If that user inputs 230 | ``10:00:00 pm``, the widget will convert it to UTC time and 24-hour format. 231 | The final value you'll get is ``16:30:00``. 232 | 233 | This timezone conversion only happens on the ``datetime`` field. It doesn't affect ``date`` field 234 | or ``time`` field. 235 | 236 | Formatting datetime 237 | ~~~~~~~~~~~~~~~~~~~ 238 | 239 | The widget keeps the datetime value as an ISO string for JSON compatibility. 240 | 241 | However, you may want to format a date value such as to display in the templates 242 | in a user-friendly format. 243 | 244 | Formatting datetime in templates 245 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 246 | 247 | django-jsonform provides a few template filters to convert the date string to a 248 | ``datetime`` object so you can use it with Django's ``date`` filter. 249 | 250 | You can use the :tfilter:`parse_datetime` filter (*New in version 2.9*) for this: 251 | 252 | .. code-block:: html 253 | 254 | 255 | {% load django_jsonform %} 256 | 257 | {{ date_string | parse_datetime }} 258 | 259 | 260 | {{ date_string | parse_datetime | date:'d M, Y' }} 261 | 262 | 263 | 264 | All the available tags and filters are listed in :doc:`Template tags and filters ` 265 | document. 266 | 267 | Formatting datetime in Python code 268 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 269 | 270 | To format datetime string in Python code, you'll have to first convert the string 271 | to Python's ``datetime`` object: 272 | 273 | .. code-block:: python 274 | 275 | from datetime import datetime 276 | 277 | date_string = '2022-02-06T15:42:11.092+00:00' # ISO string 278 | 279 | date = datetime.fromisoformat(date_string) 280 | 281 | # ... do something with the object ... 282 | -------------------------------------------------------------------------------- /docs/guide/javascript.rst: -------------------------------------------------------------------------------- 1 | JavaScript API 2 | ============== 3 | 4 | .. versionadded:: 2.11 5 | 6 | This document describes the JavaScript API available for django-jsonform widget. 7 | 8 | **Some use cases**: 9 | 10 | - Dynamically modifying schema directly in the browser 11 | - Enabling/disabling inputs dynamically 12 | - Updating choices dynamically 13 | - Making AJAX requests on value changes 14 | 15 | 16 | API 17 | --- 18 | 19 | .. note:: 20 | 21 | django-jsonform uses `react-json-form `_ 22 | under the hood. 23 | 24 | So, this is a shortened documentation of the actual API. We'll only look at 25 | the functions which concern the widget. 26 | 27 | 28 | ``reactJsonForm.getFormInstance(id)`` 29 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 30 | 31 | Use this function to get an instance of the form widget. 32 | 33 | The ``id`` is the ID of the widget container which looks like this: ``id__jsonform``. 34 | 35 | E.g., if your model's JSONField is called "my_field", then the container id will be 36 | ``id_my_field_jsonform``. 37 | 38 | You can also do *Right-click > Inspect element* on the form widget to view the ID of 39 | the container. 40 | 41 | .. code-block:: javascript 42 | 43 | var form = reactJsonForm.getFormInstance('id_my_field_jsonform'); 44 | 45 | 46 | ``formInstance.addEventListener(event, callback)`` 47 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 48 | 49 | Use this function to add a ``callback`` function for an ``event``. It will be called 50 | every time the event occurs. 51 | 52 | The callback will receive an object containing these keys: 53 | 54 | - ``data``: The data of the widget 55 | - ``schema``: The schema of the widget 56 | - ``prevData``: Previous data (before the event) 57 | - ``prevSchema``: Previous schema (before the event) 58 | 59 | .. code-block:: javascript 60 | 61 | function onChangeHandler(e) { 62 | // do something ... 63 | } 64 | 65 | var form = reactJsonForm.getFormInstance('id_my_field_jsonform'); 66 | 67 | form.addEventListener('change', onChangeHandler); 68 | 69 | 70 | ``formInstance.update(config)`` 71 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 72 | 73 | Use this function to update the schema or the data of the widget. 74 | 75 | 76 | The ``config`` is a JavaScript object (dict) which looks like this: 77 | 78 | .. code-block:: javascript 79 | 80 | var config = { 81 | schema: ..., 82 | data: ..., 83 | } 84 | 85 | form.update(config); 86 | 87 | 88 | .. important:: 89 | If you call the ``update`` function from a ``change`` event listener, it is important 90 | that you call it conditionally. Otherwise, it might lead to an infinite loop. 91 | 92 | For example, call this function if the current data (``data``) and the previous data 93 | (``prevData``) are not the same. This way you can avoid the infinite loop. 94 | 95 | 96 | ``formInstance.getData()`` 97 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 98 | 99 | Returns the current data of the form instance. 100 | 101 | ``formInstance.getSchema()`` 102 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 103 | 104 | Returns the current schema of the form instance. 105 | 106 | 107 | Practical example 108 | ----------------- 109 | 110 | **Updating choices dynamically**: Let's look at an example where there are two select inputs and choices of the 111 | second input depends on the first input. 112 | 113 | 114 | Interactive Demo 115 | ~~~~~~~~~~~~~~~~ 116 | 117 | In the following demo, **Vehicle** input's ``choices`` and ``helpText`` will change 118 | dynamically depending upon the value of the **Category** input. 119 | 120 | .. raw:: html 121 | 122 | 123 | 128 | 129 | 130 | Schema 131 | ~~~~~~ 132 | 133 | The schema for this demo: 134 | 135 | .. code-block:: python 136 | 137 | { 138 | 'type': 'object', 139 | 'title': 'Mode of transportation', 140 | 'keys': { 141 | 'category': { 142 | 'type': 'string', 143 | 'choices': ['Land', 'Water', 'Air'] 144 | }, 145 | 'vehicle': { 146 | 'type': 'string', 147 | 'choices': [] # vehicle choices will be added dynamically 148 | } 149 | } 150 | } 151 | 152 | 153 | JavaScript code 154 | ~~~~~~~~~~~~~~~ 155 | 156 | Following is the code which is used in the demo above: 157 | 158 | .. code-block:: javascript 159 | 160 | // my-script.js 161 | 162 | window.addEventListener('load', function() { 163 | /* We want to run this code after all other scripts have been loaded */ 164 | 165 | if (window.reactJsonForm) { 166 | /* We put this inside a condition because 167 | * we only want it to run on those pages where 168 | * django-jsonform widget is loaded 169 | */ 170 | var form = reactJsonForm.getFormInstance('id_my_field_jsonform'); 171 | form.addEventListener('change', onJsonFormChange); 172 | } 173 | }); 174 | 175 | 176 | var vehicleChoiceMap = { 177 | 'Land': ['Car', 'Bus', 'Train'], 178 | 'Water': ['Ship', 'Boat', 'Submarine'], 179 | 'Air': ['Aeroplane', 'Rocket'], 180 | }; 181 | 182 | 183 | function onJsonFormChange(e) { 184 | var data = e.data; // current data 185 | var prevData = e.prevData; // previous data (before this event) 186 | 187 | var schema = e.schema; // current schema 188 | var prevSchema = e.prevSchema; // previous schema (before this event) 189 | 190 | var selectedCategory = data.category; 191 | 192 | if (!selectedCategory) { 193 | /* no category selected yet, exit the function */ 194 | return; 195 | } 196 | 197 | if (selectedCategory === prevData.category) { 198 | /* category hasn't changed, no need to update choices */ 199 | return; 200 | } 201 | 202 | schema.keys.vehicle.choices = vehicleChoiceMap[selectedCategory]; 203 | schema.keys.vehicle.helpText = "Select " + selectedCategory + " vehicle"; 204 | data.vehicle = ''; // reset previously selected vehicle 205 | 206 | form.update({ 207 | schema: schema, 208 | data: data 209 | }) 210 | } 211 | 212 | 213 | Loading your custom JS file on the admin page 214 | --------------------------------------------- 215 | 216 | You can use the ``Media`` class to load your custom JS files in the admin page. 217 | 218 | Quickest way is via your admin class: 219 | 220 | .. code-block:: python 221 | 222 | # models.py 223 | 224 | class MyAdmin(admin.ModelAdmin): 225 | ... 226 | class Media: 227 | js = ('path/to/my-script.js',) 228 | 229 | There are other ways as well (and perhaps more suitable in certain cases) for loading your 230 | custom files, such as by subclassing the widget. 231 | 232 | .. seealso:: 233 | 234 | `Form Assets (the Media class) `__ 235 | Django's documentation on the ``Media`` class. 236 | -------------------------------------------------------------------------------- /docs/guide/translations.rst: -------------------------------------------------------------------------------- 1 | Lazy translations 2 | ================= 3 | 4 | .. versionadded:: 2.7 5 | 6 | You may want to display field labels or choice names in a particular user's local 7 | language. For those cases, the schema also supports lazy translations: 8 | 9 | .. code-block:: python 10 | 11 | from django.utils.translation import gettext_lazy as _ 12 | 13 | 14 | { 15 | 'type': 'string', 16 | 'title': _('Occupation'), 17 | 'choices': [ 18 | {'value': 'teacher', 'title': _('Teacher')}, 19 | {'value': 'doctor', 'title': _('Doctor')}, 20 | {'value': 'engineer', 'title': _('Engineer')}, 21 | 'default': 'teacher' 22 | } 23 | -------------------------------------------------------------------------------- /docs/guide/validation.rst: -------------------------------------------------------------------------------- 1 | Validation 2 | ========== 3 | 4 | .. versionadded:: 2.12 5 | 6 | django-jsonform supports basic data validation by default and appropriate error 7 | messages are displayed below the input fields in case any value is invalid. 8 | 9 | Validation keywords 10 | ------------------- 11 | 12 | For ``array`` type 13 | ~~~~~~~~~~~~~~~~~~ 14 | 15 | =============== =========== 16 | Keyword Description 17 | =============== =========== 18 | ``minItems`` (*Integer*) Minimum number or required items. 19 | ``maxItems`` (*Integer*) Maximum number of allowed items. 20 | ``uniqueItems`` (*Boolean*) Whether all items must be unique or not. 21 | =============== =========== 22 | 23 | For ``object`` type 24 | ~~~~~~~~~~~~~~~~~~~ 25 | 26 | =============== =========== 27 | Keyword Description 28 | =============== =========== 29 | ``required`` (*List*) A list containing names of required object properties (keys). 30 | =============== =========== 31 | 32 | .. versionchanged:: 2.16.0 33 | Support for ``required`` keyword for object properties was added. 34 | 35 | 36 | For ``string`` type 37 | ~~~~~~~~~~~~~~~~~~~ 38 | 39 | ============= =========== 40 | Keyword Description 41 | ============= =========== 42 | ``required`` (*Boolean*) Whether this field is required or not. 43 | ``minLength`` (*Integer*) Minimum length of the value. 44 | ``maxLength`` (*Integer*) Maximum allowed length of the value. 45 | ============= =========== 46 | 47 | For ``integer`` and ``number`` type 48 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 49 | 50 | ==================== =========== 51 | Keyword Description 52 | ==================== =========== 53 | ``required`` (*Boolean*) Whether this field is required or not. 54 | ``minimum`` (*Integer/Float*) Minimum allowed value including this limit. 55 | ``maximum`` (*Integer/Float*) Maximum allowed value including this limit. 56 | ``exclusiveMinimum`` (*Integer/Float*) Minimum allowed value excluding this limit. 57 | ``exclusiveMaximum`` (*Integer/Float*) Maximum allowed value excluding this limit. 58 | ==================== =========== 59 | 60 | For ``boolean`` type 61 | ~~~~~~~~~~~~~~~~~~~~ 62 | 63 | ============= =========== 64 | Keyword Description 65 | ============= =========== 66 | ``required`` (*Boolean*) Whether this field is required or not. 67 | ============= =========== 68 | 69 | Example 70 | ~~~~~~~ 71 | 72 | .. code-block:: python 73 | :emphasize-lines: 5, 11, 12, 16 74 | 75 | # Schema 76 | 77 | { 78 | 'type': 'array', 79 | 'minItems': 1, 80 | 'items': { 81 | 'type': 'object', 82 | 'properties': { 83 | 'name': { 84 | 'type': 'string', 85 | 'required': True, 86 | 'maxLength': 30 87 | }, 88 | 'age': { 89 | 'type': 'integer', 90 | 'minimum': '18' 91 | } 92 | } 93 | } 94 | } 95 | 96 | 97 | Custom validation 98 | ----------------- 99 | 100 | There are many ways to validate a field in Django. 101 | 102 | Two of the most basic ways are either by using the ``Model.clean()`` method or by 103 | passing a ``validators`` argument to the model field. 104 | 105 | The problem with these validation methods is that there is no way to provide 106 | error messages for particular input fields. 107 | 108 | The error message you return will be displayed above the JSON form widget. 109 | 110 | 111 | Basic validation 112 | ~~~~~~~~~~~~~~~~ 113 | 114 | - ``Model.clean()``: Refer to Django docs on using `Model.clean() `__ method. 115 | - ``validators``: Refer Django docs on using `Validators `__. 116 | 117 | 118 | Advanced validation 119 | ~~~~~~~~~~~~~~~~~~~ 120 | 121 | Advanced validation allows you to provide error messages for each input field 122 | which will be displayed right below them. 123 | 124 | Creating a form 125 | ^^^^^^^^^^^^^^^ 126 | 127 | For this, you're required to create a custom form class for the admin page. 128 | 129 | .. code-block:: python 130 | :emphasize-lines: 14,15 131 | 132 | # models.py 133 | 134 | class ShoppingList(models.Model): 135 | items = JSONField(schema=...) 136 | 137 | ... 138 | 139 | # admin.py 140 | 141 | class ShoppingListForm(forms.ModelForm): 142 | def __init__(self, *args, **kwargs): 143 | super().__init__(*args, **kwargs) 144 | 145 | # set your validators on the form field 146 | self.fields['items'].validators = [items_validator] 147 | 148 | 149 | class ShoppingListAdmin(admin.ModelAdmin): 150 | form = ShoppingListForm 151 | 152 | 153 | Writing the validator 154 | ^^^^^^^^^^^^^^^^^^^^^ 155 | 156 | In your validator function, instead of raising ``ValidationError`` 157 | you must raise :class:`~django_jsonform.exceptions.JSONSchemaValidationError`. This exception allows you to pass 158 | error messages for individual input field in the widget. 159 | 160 | We'll use the :class:`~django_jsonform.utils.ErrorMap` helper class to create 161 | the mapping of field names to error messages: 162 | 163 | .. code-block:: python 164 | 165 | from django_jsonform.exceptions import JSONSchemaValidationError 166 | from django_jsonform.utils import ErrorMap 167 | 168 | def items_validator(value): 169 | error_map = ErrorMap() 170 | 171 | if value[0] != 'Banana': 172 | error_map.set(coords=[0], msg='First item in shopping list must be Banana') 173 | 174 | if value[1] != 'Eggs': 175 | error_map.set(coords=[1], msg='Second item in shopping list must be Eggs') 176 | 177 | # do other validations ... 178 | 179 | if error_map: 180 | # if error_map has keys raise error 181 | raise JSONSchemaValidationError( 182 | 'Please correct errors below', 183 | error_map=error_map # pass error_map to exception 184 | ) 185 | 186 | 187 | For passing multiple error messages for one input, use a list: 188 | 189 | .. code-block:: python 190 | 191 | # using ErrorMap.set() 192 | error_map.set(coords=[0], msg=['First error', 'Second error', ...]) 193 | 194 | # or useing ErrorMap.append() 195 | error_map.append(coords=[0], msg=['First error', 'Second error', ...]) 196 | 197 | 198 | See :class:`~django_jsonform.utils.ErrorMap` class's docs for more details on its 199 | usage. 200 | 201 | 202 | Providing errors for deeply nested inputs 203 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 204 | 205 | The keys in the ``error_map`` dict are *"coordinates"* of the invalid input fields 206 | (see :doc:`/guide/coordinates` page to learn more). 207 | 208 | For example, if each shopping list item has a ``name`` and a ``quantity`` and you want 209 | to display an error message under the first item's ``quantity`` input, you'll do this: 210 | 211 | .. code-block:: python 212 | 213 | from django_jsonform.utils import ErrorMap 214 | 215 | error_map = ErrorMap() 216 | 217 | # error message for 'quantity' of '0' (first item) 218 | error_map.set(coords=[0, 'quantity'], msg='Minimum quantity must be 5') 219 | 220 | 221 | .. _validate-on-submit: 222 | 223 | Validating data in the browser before form submission 224 | ----------------------------------------------------- 225 | 226 | The JavaScript part of this widget supports optional in-browser validation. 227 | 228 | The data will be validated before the form is submitted. If there are any errors, 229 | the form will not submit and user will be asked to correct them. 230 | 231 | This method only supports basic validation. When the data has passed the browser 232 | validation tests, it will be validated once again on the server with your custom 233 | validation rules. 234 | 235 | To enable in-browser validation, set the ``validate_on_submit`` attribute to 236 | ``True`` on the widget. 237 | 238 | There are two ways to do this: 239 | 240 | **Option 1**: Changing the attribute on the widget: 241 | 242 | .. code-block:: python 243 | :emphasize-lines: 7 244 | 245 | # Option 1: In form's __init__ method 246 | 247 | class ShoppingListForm(forms.ModelForm): 248 | def __init__(self, *args, **kwargs): 249 | super().__init__(*args, **kwargs) 250 | 251 | self.fields['items'].widget.validate_on_submit = True 252 | 253 | 254 | **Option 2**: Alternatively, if you're overriding the widget in the ``Meta`` class, 255 | you can pass the ``validate_on_submit`` argument to the widget: 256 | 257 | .. code-block:: python 258 | :emphasize-lines: 6 259 | 260 | # Option 2: In form's Meta class 261 | 262 | class ShoppingListForm(forms.ModelForm): 263 | class Meta: 264 | widgets: { 265 | 'items': JSONFormWidget(schema=..., validate_on_submit=True) 266 | } 267 | 268 | 269 | Built-in validators 270 | ------------------- 271 | 272 | .. module:: django_jsonform.validators 273 | :synopsis: Built-in validators 274 | 275 | ``JSONSchemaValidator`` 276 | ~~~~~~~~~~~~~~~~~~~~~~~ 277 | 278 | .. class:: JSONSchemaValidator(schema) 279 | 280 | .. versionadded:: 2.12 281 | 282 | This is the default validator used for validating the submitted forms. 283 | 284 | **Parameters**: 285 | 286 | .. attribute:: schema 287 | :type: dict 288 | 289 | Schema to use for validation. 290 | 291 | **Methods**: 292 | 293 | .. method:: validate(data) 294 | 295 | Validates the ``data`` against the schema provided to the validator instance. 296 | 297 | If the data is invalid, it will raise :class:`~django_jsonform.exceptions.JSONSchemaValidationError` 298 | exception. 299 | 300 | **Usage**: 301 | 302 | .. code-block:: python 303 | 304 | from django_jsonform.validators import JSONSchemaValidator 305 | 306 | # create a validator instance 307 | validator = JSONSchemaValidator(schema=...) 308 | 309 | # validate the data 310 | validate(data) 311 | 312 | # if the data is invalid, JSONSchemaValidationError will be raised 313 | 314 | 315 | Exceptions 316 | ---------- 317 | 318 | .. module:: django_jsonform.exceptions 319 | :synopsis: Exceptions 320 | 321 | ``JSONSchemaValidationError`` 322 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 323 | 324 | .. class:: JSONSchemaValidationError(message, code=None, params=None, error_map=None) 325 | 326 | .. versionadded:: 2.12 327 | 328 | It is a subclass of Django's ``ValidationError``. It accepts one extra argument 329 | called ``error_map``. 330 | 331 | **Parameters**: 332 | 333 | .. attribute:: error_map 334 | :type: ErrorMap 335 | 336 | An instance of :class:`~django_jsonform.utils.ErrorMap` class for providing 337 | the errors for widget's input fields. 338 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2 | =============== 3 | 4 | **django-jsonform** provides a user-friendly form interface for editing JSON data 5 | in Django admin. 6 | 7 | You declare your data structure using JSON schema and it will generate a dynamic 8 | form for creating and editing the data. 9 | 10 | It also supports Postgres ``ArrayField`` with multiple levels of nesting. 11 | 12 | 13 | Project links 14 | ------------- 15 | 16 | - Current version: |version| (:doc:`Release notes `) 17 | - `Source code (Github) `__ 18 | - `Live Demos & Playground `__ 19 | 20 | 21 | Table of contents 22 | ----------------- 23 | 24 | .. toctree:: 25 | :maxdepth: 2 26 | 27 | Home 28 | installation 29 | quickstart 30 | guide/index 31 | schema 32 | fields-and-widgets 33 | templatetags 34 | settings 35 | utils 36 | examples 37 | troubleshooting 38 | releases/index 39 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Requirements: 5 | 6 | - Python >= 3.4 7 | - Django >= 2.0 8 | 9 | Install using pip: 10 | 11 | .. code-block:: sh 12 | 13 | $ pip install django-jsonform 14 | 15 | 16 | We also upload `pre-built packages `_ 17 | on Github in case pip or PyPI server isn't working. 18 | 19 | 20 | Update your project's settings: 21 | 22 | .. code-block:: python 23 | 24 | # settings.py 25 | 26 | INSTALLED_APPS = [ 27 | # ... 28 | 'django_jsonform' 29 | ] 30 | 31 | 32 | .. admonition:: Upgrading notes 33 | 34 | When upgrading from an older version of this library, please ensure that your 35 | browser is loading the latest static JavaScript files that come with this library: 36 | 37 | - In the development environment, clear the browser cache. 38 | - In the production environment, you must run the ``collectstatic`` command to update 39 | the static files. 40 | 41 | 42 | Next, go to :doc:`quickstart` page for basic usage instructions. -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quickstart 2 | ========== 3 | 4 | ``django-jsonform`` allows you to edit JSON data by supplying a *schema* for the 5 | data structure. 6 | 7 | Shopping list 8 | ------------- 9 | 10 | Suppose we want to create a model for saving shopping lists. A typical shopping 11 | list looks like this: ``['eggs', 'milk', 'juice']``. It is basically a 12 | **list of strings**. 13 | 14 | Sample model 15 | ------------ 16 | 17 | ``django-jsonform`` provides a custom :class:`~django_jsonform.models.fields.JSONField` 18 | for your convenience. You can also use the :class:`widget ` 19 | but it requires a little more work to set up. 20 | 21 | Here's a model with sample schema: 22 | 23 | .. code-block:: python 24 | 25 | # models.py 26 | 27 | from django.db import models 28 | from django_jsonform.models.fields import JSONField 29 | 30 | 31 | class ShoppingList(models.Model): 32 | ITEMS_SCHEMA = { 33 | 'type': 'array', # a list which will contain the items 34 | 'items': { 35 | 'type': 'string' # items in the array are strings 36 | } 37 | } 38 | 39 | items = JSONField(schema=ITEMS_SCHEMA) 40 | date_created = models.DateTimeField(auto_now_add=True) 41 | 42 | 43 | Admin 44 | ----- 45 | 46 | Register your model for the admin site: 47 | 48 | .. code-block:: python 49 | 50 | # admin.py 51 | 52 | from django.contrib import admin 53 | from myapp.models import ShoppingList 54 | 55 | 56 | admin.site.register(ShoppingList) 57 | 58 | 59 | Now go to the admin site and visit the *"Add new"* shopping list page. The form should 60 | look something like this: 61 | 62 | .. image:: _static/quickstart.gif 63 | :alt: Animated screenshot of admin page 64 | 65 | 66 | Next steps 67 | ---------- 68 | 69 | - The :doc:`User's guide ` contains further details about various 70 | input types, uploading files and other features. 71 | - See :doc:`schema` for a reference on the supported schema. 72 | - See :doc:`fields-and-widgets` for available fields and widgets. 73 | - See :doc:`examples` for sample schemas for declaring complex data structures. -------------------------------------------------------------------------------- /docs/releases/index.rst: -------------------------------------------------------------------------------- 1 | Release notes 2 | ============= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | v2.23.2 8 | v2.23.1 9 | v2.23.0 10 | v2.22.0 11 | v2.21.5 12 | v2.21.4 13 | v2.21.3 14 | v2.21.2 15 | v2.21.1 16 | v2.21.0 17 | v2.20.2 18 | v2.20.1 19 | v2.20.0 20 | v2.19.1 21 | v2.19.0 22 | v2.18.0 23 | v2.17.4 24 | v2.17.3 25 | v2.17.2 26 | v2.17.1 27 | v2.17.0 28 | v2.16.1 29 | v2.16.0 30 | v2.15.0 31 | v2.14.0 32 | v2.13.0 33 | v2.12.0 34 | v2.11.1 35 | v2.11.0 36 | v2.10.1 37 | v2.10.0 38 | v2.9.0 39 | v2.8.1 40 | v2.8.0 41 | v2.7.0 42 | v2.6.0 43 | v2.5.0 44 | v2.4.0 45 | v2.3.0 46 | v2.2.1 47 | v2.2.0 48 | v2.1.0 49 | v2.0.0 50 | v1.0.0 -------------------------------------------------------------------------------- /docs/releases/v1.0.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 1.0.0 release notes 2 | =================================== 3 | 4 | Sep 2, 2021 5 | ----------- 6 | 7 | The first release. 8 | 9 | This version (v1.0.0) supports Django >= 2.0 and Python >= 3.4. 10 | 11 | react-json-form (JavaScript) 12 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 13 | 14 | Uses `react-json-form `_ version 1.3.0. -------------------------------------------------------------------------------- /docs/releases/v2.0.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.0.0 release notes 2 | =================================== 3 | 4 | 5 | Sep 06, 2021 6 | ------------ 7 | 8 | This version is fully backwards compatible with the previous version (v1.0.0). 9 | 10 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 11 | 12 | 13 | What's new 14 | ~~~~~~~~~~ 15 | 16 | :class:`~django_jsonform.models.fields.JSONField` 17 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | A model field called :class:`~django_jsonform.models.fields.JSONField` has been 20 | added which makes it more convenient to set up the editing form widget. 21 | 22 | :class:`~django_jsonform.models.fields.ArrayField` 23 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 24 | 25 | A model field called :class:`~django_jsonform.models.fields.ArrayField` has been 26 | added which provides a nice, dynamic form for editing the Postgres ``ArrayField``. 27 | 28 | react-json-form (JavaScript) 29 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 30 | 31 | `react-json-form `_ has been updated 32 | to version 1.4.1. 33 | 34 | Minor changes 35 | ^^^^^^^^^^^^^ 36 | 37 | Some small improvements to the widget's css. 38 | -------------------------------------------------------------------------------- /docs/releases/v2.1.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.1.0 release notes 2 | =================================== 3 | 4 | Sep 09, 2021 5 | ------------ 6 | 7 | This version is fully backwards compatible. 8 | 9 | What's new 10 | ~~~~~~~~~~ 11 | 12 | Callable ``schema`` 13 | ^^^^^^^^^^^^^^^^^^^ 14 | 15 | ``schema`` can now be a callable. This allows for :ref:`specifying choices dynamically `, 16 | among other things. 17 | 18 | Minor changes 19 | ^^^^^^^^^^^^^ 20 | 21 | Improvements to the widget's CSS: 22 | 23 | - Fixed: the remove button overlapped the input field on mobile. 24 | - Fixed: ``select`` input wasn't the same width as other inputs. 25 | -------------------------------------------------------------------------------- /docs/releases/v2.10.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.10.0 release notes 2 | ==================================== 3 | 4 | 5 | Jun 03, 2022 6 | ------------ 7 | 8 | This version introduces no breaking changes and is fully backwards-compatible 9 | with the previous releases (2.9.x) 10 | 11 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 12 | 13 | 14 | What's new 15 | ~~~~~~~~~~ 16 | 17 | 18 | New features 19 | ^^^^^^^^^^^^ 20 | 21 | - ``additionalProperties`` can accept a schema now. Earlier, ``additionalProperties`` 22 | only accepted a boolean and the new properties (keys) could only be of string type. 23 | Now you can provide a schema for new properties through this schema. 24 | - Support for :ref:`recursive nesting` objects and items. 25 | - Support for :ref:`referencing schema` (using the ``$ref`` keyword). 26 | - :class:`~django_jsonform.models.fields.JSONField` now accepts a new parameter called 27 | ``pre_save_hook`` which can be used to process or transform the JSON data before saving. 28 | 29 | 30 | Bug fixes 31 | ^^^^^^^^^ 32 | 33 | - :issue:`38`: If an array has ``minItems`` set to 0 or undefined, it will be kept blank initially. 34 | If it has a default value, it will use the default value instead of being blank. 35 | Earlier, an empty item was automatically added to an array which could be undesirable in many cases. 36 | 37 | 38 | react-json-form (JavaScript) 39 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 40 | 41 | `react-json-form `_ has been updated 42 | to version 1.12.1. 43 | -------------------------------------------------------------------------------- /docs/releases/v2.10.1.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.10.1 release notes 2 | ==================================== 3 | 4 | 5 | Jun 10, 2022 6 | ------------ 7 | 8 | django-jsonform v2.10.1 fixes a "high" severity security vulnerability which affects 9 | all previous versions. 10 | 11 | 12 | XSS (Cross Site Scripting) vulnerability in the admin form 13 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 14 | 15 | django-jsonform stores the raw JSON data of the database field in a hidden 16 | ``textarea`` on the admin page. 17 | 18 | However, that data was kept in the ``textarea`` after unescaping it using the 19 | ``safe`` template filter. This opens up possibilities for XSS attacks. 20 | 21 | This only affects the admin pages where the django-jsonform is rendered. 22 | -------------------------------------------------------------------------------- /docs/releases/v2.11.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.11.0 release notes 2 | ==================================== 3 | 4 | 5 | Aug 16, 2022 6 | ------------ 7 | 8 | This release brings plenty of bugfixes, some exciting new features and some 9 | deprecations. 10 | 11 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 12 | 13 | 14 | What's new 15 | ~~~~~~~~~~ 16 | 17 | 18 | Deprecation notice 19 | ^^^^^^^^^^^^^^^^^^ 20 | 21 | - ``JSONFORM_UPLOAD_HANDLER`` 22 | 23 | The ``JSONFORM_UPLOAD_HANDLER`` setting has been deprecated and will be removed 24 | in future. 25 | Please read about the new way to upload files in the :ref:`Uploading files ` 26 | document. 27 | 28 | 29 | New features 30 | ^^^^^^^^^^^^ 31 | 32 | - Brand new :doc:`JavaScript API ` for controlling the 33 | widget in the browser. 34 | - New :ref:`file upload modal dialog ` which provides the ability 35 | to browse files form the server while uploading. 36 | - Support for ``enum`` keyword (alias for ``choices``). 37 | - Support for ``placeholder`` keyword. 38 | - Support for ``date-time`` keyword (alias for ``datetime``). 39 | - Support for ``handler`` keyword for string input. This can be used for 40 | specifying the url for the file upload handler on a per input field basis. 41 | - :class:`~django_jsonform.models.fields.JSONField` now accepts a new parameter 42 | called ``file_handler`` which can be used for specifying the url of the file 43 | handler on a per widget basis. 44 | - Now django-jsonform settings will be namespaced under :setting:`DJANGO_JSONFORM` 45 | setting. 46 | 47 | 48 | Bug fixes 49 | ^^^^^^^^^ 50 | 51 | - :issue:`45`: Default value for number and boolean types was ignored if the 52 | default value was 0 or False. 53 | - :issue:`46`: Fixed a bug due to which the multiselect widget didn't work on 54 | top-level arrays. 55 | - :issue:`47`: Fixed a bug in CSS when two widget fields were displayed in a 56 | single fieldset row. 57 | - :issue:`48`: Array's ``minItems`` keyword was ignored if default value for 58 | array was provided or if the form field had initial data. 59 | - Fixed a bug to make select input respect falsy options. Earlier, the select input 60 | would not update the selected value if a falsy option was selected. 61 | - Minor improvements and fixes in widget's CSS. 62 | 63 | 64 | react-json-form (JavaScript) 65 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 66 | 67 | `react-json-form `_ has been updated 68 | to version 2.0.0. 69 | 70 | ---- 71 | 72 | Finally, huge thanks to... 73 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 74 | 75 | - All my sponsors for their support. I really appreciate it. 76 | - All the people for contributing bug reports and improvement suggestions. -------------------------------------------------------------------------------- /docs/releases/v2.11.1.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.11.1 release notes 2 | ==================================== 3 | 4 | 5 | Aug 30, 2022 6 | ------------ 7 | 8 | This is a minor release. 9 | 10 | 11 | What's new 12 | ~~~~~~~~~~ 13 | 14 | 15 | Bugfixes 16 | ^^^^^^^^ 17 | 18 | - :issue:`54`: Multiselect widget didn't work with integer types. 19 | 20 | 21 | react-json-form (JavaScript) 22 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 23 | 24 | `react-json-form `_ has been updated 25 | to version 2.0.2. -------------------------------------------------------------------------------- /docs/releases/v2.12.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.12.0 release notes 2 | ==================================== 3 | 4 | 5 | Sep 17, 2022 6 | ------------ 7 | 8 | This release brings some exciting new features and a few bugfixes. 9 | 10 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 11 | 12 | 13 | New features 14 | ~~~~~~~~~~~~ 15 | 16 | - **Data validation** 17 | 18 | Support for :doc:`data validation `. 19 | 20 | - **Validation keywords** 21 | 22 | Support for validation keywords — ``required``, ``minLength``, ``maxLength``, 23 | ``minimum``, ``maximum``, ``exclusiveMinimum``, ``exclusiveMaximum``, 24 | ``uniqueItems``. 25 | 26 | - **Autocomplete widget** 27 | 28 | New :doc:`autocomplete widget ` which can load choices via 29 | AJAX requests. 30 | 31 | - **File deletion** 32 | 33 | A delete button has been added in Media Library thumbnails which will send a 34 | ``DELETE`` request to file handler endpoint. In addition to that, ``DELETE`` 35 | requests will automatically be sent to the server when *Clear* button is clicked 36 | or when exiting page without form submission. 37 | 38 | - **Rename choices label keyword to title** 39 | 40 | For consistency with JSON schema, choice ``label`` keyword has been renamed to 41 | ``title``. However, the ``label`` keyword will still continue to work. 42 | 43 | - **Range input** 44 | 45 | Support for range input widget. Earlier range input was added using the ``format`` 46 | keyword, but as a range input also returns a number value (no need for a specialised format). 47 | Hence, it didn't make sense to use range as a format. So, now range inputs can 48 | be created using the ``widget`` keyword. 49 | 50 | - **Time widget improvements** 51 | 52 | Time widget's input spinner is now circular i.e. after reaching maximum or minimum 53 | value, it will start over (e.g. after 12 for hours, it will go to 0 if the hour is 54 | incremented and will go from 0 to 12 if decremented). 55 | 56 | 57 | Bug fixes 58 | ~~~~~~~~~ 59 | 60 | - :issue:`58`: Overriding the widget in custom form's ``Meta`` class was not 61 | respected. 62 | - :issue:`61`: Widget didn't accept ``attrs`` argument. 63 | - Minor improvements and fixes in widget's CSS. 64 | 65 | 66 | react-json-form (JavaScript) 67 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 68 | 69 | `react-json-form `_ has been updated 70 | to version 2.1.0. 71 | 72 | 73 | Thank you 74 | ~~~~~~~~~ 75 | 76 | I want to thank all the people who contributed bug reports, feature requests 77 | and improvement suggestions. 78 | -------------------------------------------------------------------------------- /docs/releases/v2.13.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.13.0 release notes 2 | ==================================== 3 | 4 | 5 | Oct 09, 2022 6 | ------------ 7 | 8 | This release brings some minor but important changes. 9 | 10 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 11 | 12 | 13 | Breaking changes 14 | ~~~~~~~~~~~~~~~~ 15 | 16 | - **Widget template** 17 | 18 | This concerns you **if you've overridden the widget's template** in your projects. 19 | 20 | There have been a lot of changes to the template. Please `take a look at the updated template `_. 21 | 22 | The context variables sent by the widget have also been renamed. `Take a look at 23 | the updated context variables `_. 24 | 25 | 26 | - **JSONFormWidget class** 27 | 28 | This concerns you **if you've subclassed the** ``JSONFormWidget`` **class** in your projects. 29 | 30 | Specifically, the context variables returned by the ``render`` method of widget have 31 | been changed/renamed. `Take a look at the updated render method `_. 32 | 33 | 34 | New features 35 | ~~~~~~~~~~~~ 36 | 37 | - **Support for django-nested-admin** 38 | 39 | Support for `django-nested-admin `_ is added. Code contributed by `Trent Holliday `_. 40 | 41 | 42 | Minor Changes 43 | ~~~~~~~~~~~~~ 44 | 45 | - **Dropped jQuery dependency** 46 | 47 | Earlier releases relied upon jQuery to generate the widget for Django's Inline formsets. 48 | This release get rids of the jQuery calls. 49 | 50 | - **File Clear button** 51 | 52 | File input's *Clear* button will not send a ``DELETE`` request now. This is done 53 | to state in-tune with Django's *Clear* button which only empties the file input, 54 | and doesn't delete it. A dedicated Delete button may be added in future. 55 | 56 | 57 | Bug fixes 58 | ~~~~~~~~~ 59 | 60 | - Some bugfixes in widget's JavaScript library. 61 | 62 | 63 | react-json-form (JavaScript) 64 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 65 | 66 | `react-json-form `_ has been updated 67 | to version 2.2.0. 68 | 69 | 70 | Thank you 71 | ~~~~~~~~~ 72 | 73 | I want to thank all the people who contributed code, bug reports, feature requests 74 | and improvement suggestions. 75 | -------------------------------------------------------------------------------- /docs/releases/v2.14.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.14.0 release notes 2 | ==================================== 3 | 4 | 5 | Nov 01, 2022 6 | ------------ 7 | 8 | This release brings a small feature. 9 | 10 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 11 | 12 | 13 | New features 14 | ~~~~~~~~~~~~ 15 | 16 | - **Overriding** ``file-url`` **input field** 17 | 18 | Use ``widget: 'fileinput'`` to display a simple file input field for ``file-url``. 19 | It is useful in those cases where you don't want to open a modal for choosing 20 | images. 21 | 22 | react-json-form (JavaScript) 23 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 24 | 25 | `react-json-form `_ has been updated 26 | to version 2.3.0. 27 | 28 | 29 | Thank you 30 | ~~~~~~~~~ 31 | 32 | I want to thank my sponsors and all the people who contributed code, bug reports, 33 | feature requests and improvement suggestions. 34 | -------------------------------------------------------------------------------- /docs/releases/v2.15.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.15.0 release notes 2 | ==================================== 3 | 4 | 5 | Nov 21, 2022 6 | ------------ 7 | 8 | This release brings plenty of bug fixes and one breaking change 9 | 10 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 11 | 12 | 13 | New features 14 | ~~~~~~~~~~~~ 15 | 16 | - :class:`~django_jsonform.utils.ErrorMap`: A new helper class for setting 17 | error messages with custom validation. 18 | 19 | 20 | Bug fixes 21 | ~~~~~~~~~ 22 | 23 | - :issue:`69`: Allow hyphens in schema object keys. 24 | - :issue:`70`: Made the widget compatible with HTMX. 25 | - :issue:`81`: Fixed a bug due to which the error messages were not displayed under input fields. 26 | - :issue:`82`: Fixed a bug to display error messages from multiple validators. 27 | - Minor fixes: :issue:`71`, :issue:`73`, :issue:`79` 28 | 29 | 30 | Breaking changes 31 | ~~~~~~~~~~~~~~~~ 32 | 33 | - **Custom validation** 34 | 35 | Due to fixing issue :issue:`69` to allow hyphens (dashes) schema object keys 36 | (i.e. field names), the you should now use the :class:`~django_jsonform.utils.ErrorMap` 37 | helper class to construct the ``error_map`` object. 38 | 39 | react-json-form (JavaScript) 40 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 41 | 42 | `react-json-form `_ has been updated 43 | to version 2.4.0. 44 | 45 | 46 | Thank you 47 | ~~~~~~~~~ 48 | 49 | I want to thank my sponsors and all the people who contributed code, bug reports, 50 | feature requests and improvement suggestions. 51 | -------------------------------------------------------------------------------- /docs/releases/v2.16.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.16.0 release notes 2 | ==================================== 3 | 4 | 5 | Mar 25, 2023 6 | ------------ 7 | 8 | This release brings some new features and some bug fixes. 9 | 10 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 11 | 12 | 13 | New features 14 | ~~~~~~~~~~~~ 15 | 16 | - **oneOf, anyOf, allOf** 17 | 18 | Support for ``oneOf``, ``anyOf`` and ``allOf`` has been added. :ref:`See Docs `. 19 | 20 | - **Required properties** 21 | 22 | Support for ``required`` keyword for object properties. 23 | 24 | - **Dark mode** 25 | 26 | Support for dark mode. 27 | 28 | 29 | Bug fixes 30 | ~~~~~~~~~ 31 | 32 | - :issue:`92`: Fixed validation for ``choices``. 33 | - :issue:`94`: Fixed a bug due to which ``True``/``False`` were treated as valid numbers. 34 | 35 | 36 | react-json-form (JavaScript) 37 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 38 | 39 | `react-json-form `_ has been updated 40 | to version 2.7.1. 41 | 42 | 43 | Thank you 44 | ~~~~~~~~~ 45 | 46 | I want to thank all the people who contributed code, bug reports, feature requests 47 | and improvement suggestions. 48 | -------------------------------------------------------------------------------- /docs/releases/v2.16.1.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.16.1 release notes 2 | ==================================== 3 | 4 | 5 | Mar 28, 2023 6 | ------------ 7 | 8 | This is a minor release. 9 | 10 | 11 | Bugfixes 12 | ^^^^^^^^ 13 | 14 | - :issue:`96`: Fixed a bug in ``required`` properties in nested objects. 15 | 16 | 17 | react-json-form (JavaScript) 18 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 19 | 20 | `react-json-form `_ has been updated 21 | to version 2.7.2. 22 | -------------------------------------------------------------------------------- /docs/releases/v2.17.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.17.0 release notes 2 | ==================================== 3 | 4 | 5 | Apr 25, 2023 6 | ------------ 7 | 8 | This release brings only one new feature. 9 | 10 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 11 | 12 | 13 | New features 14 | ~~~~~~~~~~~~ 15 | 16 | - **Hidden input** 17 | 18 | Support for hidden inputs using ``"widget": "hidden"``. 19 | 20 | 21 | react-json-form (JavaScript) 22 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 23 | 24 | `react-json-form `_ has been updated 25 | to version 2.8.0. 26 | 27 | 28 | Thank you 29 | ~~~~~~~~~ 30 | 31 | I want to thank all the people who contributed code, bug reports, feature requests 32 | and improvement suggestions. 33 | -------------------------------------------------------------------------------- /docs/releases/v2.17.1.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.17.1 release notes 2 | ==================================== 3 | 4 | 5 | Jun 01, 2023 6 | ------------ 7 | 8 | This is a minor release. 9 | 10 | 11 | Bugfixes 12 | ^^^^^^^^ 13 | 14 | - :issue:`102`: Fixed a bug due to which the type of subschema was determined incorrectly if it had a ``$ref``. 15 | 16 | 17 | react-json-form (JavaScript) 18 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 19 | 20 | `react-json-form `_ has been updated 21 | to version 2.8.1. 22 | -------------------------------------------------------------------------------- /docs/releases/v2.17.2.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.17.2 release notes 2 | ==================================== 3 | 4 | 5 | Jun 19, 2023 6 | ------------ 7 | 8 | This is a minor release. 9 | 10 | 11 | Bugfixes 12 | ^^^^^^^^ 13 | 14 | - :issue:`108`: Fixed a bug which crashed the form for schemas having ``oneOf``/``anyOf``. 15 | 16 | 17 | react-json-form (JavaScript) 18 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 19 | 20 | `react-json-form `_ has been updated 21 | to version 2.8.2. 22 | -------------------------------------------------------------------------------- /docs/releases/v2.17.3.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.17.3 release notes 2 | ==================================== 3 | 4 | 5 | Jul 24, 2023 6 | ------------ 7 | 8 | This is a minor release. 9 | 10 | 11 | Bugfixes 12 | ^^^^^^^^ 13 | 14 | - Reset select input's value to blank if the value is not present in choices. 15 | - :issue:`111`: Add choice validation for string and number types. 16 | 17 | 18 | react-json-form (JavaScript) 19 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | 21 | `react-json-form `_ has been updated 22 | to version 2.8.3. 23 | -------------------------------------------------------------------------------- /docs/releases/v2.17.4.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.17.4 release notes 2 | ==================================== 3 | 4 | 5 | Jul 24, 2023 6 | ------------ 7 | 8 | This is a minor release. 9 | 10 | 11 | Bugfixes 12 | ^^^^^^^^ 13 | 14 | - Fix a crash caused when the `type` was an array of multiple types (i.e. `{'type': ['string', 'number']}`). 15 | This only fixes the bug and doesn't add support for multiple types. 16 | 17 | 18 | react-json-form (JavaScript) 19 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | 21 | `react-json-form `_ has been updated 22 | to version 2.8.4. 23 | -------------------------------------------------------------------------------- /docs/releases/v2.18.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.18.0 release notes 2 | ==================================== 3 | 4 | 5 | Jul 27, 2023 6 | ------------ 7 | 8 | This is a minor release. 9 | 10 | 11 | New features 12 | ^^^^^^^^^^^^ 13 | 14 | - Support for the ``description`` keyword. This will allow for adding helpful text 15 | for objects and arrays. 16 | 17 | 18 | Minor changes 19 | ^^^^^^^^^^^^^ 20 | 21 | - :issue:`115`: Manually added keys (when using ``additionalProperties``) will display 22 | their key name as it is. Earlier, the name was *prettified* (i.e. capitalized, underscores removed) 23 | for display purposes, but that will no longer happen. 24 | 25 | 26 | react-json-form (JavaScript) 27 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 28 | 29 | `react-json-form `_ has been updated 30 | to version 2.9.0. 31 | -------------------------------------------------------------------------------- /docs/releases/v2.19.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.19.0 release notes 2 | ==================================== 3 | 4 | 5 | Aug 13, 2023 6 | ------------ 7 | 8 | This release brings some new features and some bugfixes. 9 | 10 | 11 | New features 12 | ^^^^^^^^^^^^ 13 | 14 | - :issue:`116`: Multiselect input will now display the selected items for a better user experience. 15 | - :issue:`119`: Support for making the whole form readonly. See docs: :ref:`Making the whole JSON form readonly`. 16 | - Support for ``readonly`` keyword on lists and dicts (arrays and objects). 17 | 18 | 19 | Bugfixes 20 | ^^^^^^^^ 21 | 22 | - Fixed :issue:`117`: Error messages for top-level arrays were not displayed. 23 | - Fixed :issue:`118`: Radio inputs' placement and styling. 24 | - Fixed: The ``readonly`` keyword had no effect on datetime and range inputs. 25 | 26 | 27 | react-json-form (JavaScript) 28 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 29 | 30 | `react-json-form `_ has been updated 31 | to version 2.11.0. 32 | -------------------------------------------------------------------------------- /docs/releases/v2.19.1.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.19.1 release notes 2 | ==================================== 3 | 4 | 5 | Sep 06, 2023 6 | ------------ 7 | 8 | This release fixes some minor bugs. 9 | 10 | 11 | Bugfixes 12 | ^^^^^^^^ 13 | 14 | - Corrected the error message for the ``maximum`` keyword (on browser-side validation). 15 | - Fixed a bug which didn't display array description when the array items were objects or arrays. 16 | 17 | 18 | react-json-form (JavaScript) 19 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | 21 | `react-json-form `_ has been updated 22 | to version 2.11.1. 23 | -------------------------------------------------------------------------------- /docs/releases/v2.2.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.2.0 release notes 2 | =================================== 3 | 4 | Sep 14, 2021 5 | ------------ 6 | 7 | This version adds no new featurs and is fully backwards compatible. 8 | 9 | Fix bad package 10 | ~~~~~~~~~~~~~~~ 11 | 12 | Templates and static files weren't included in the previous builds. This version 13 | includes all the required files. 14 | 15 | Previous versions are not usable due to this embarrasing mistake. 16 | -------------------------------------------------------------------------------- /docs/releases/v2.2.1.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.2.1 release notes 2 | =================================== 3 | 4 | Sep 21, 2021 5 | ------------ 6 | 7 | This is a minor release and adds no new features. 8 | 9 | Bugfixes 10 | ~~~~~~~~ 11 | 12 | - NoReverseMatch exception was raised if django_jsonform's urls weren't registered. 13 | -------------------------------------------------------------------------------- /docs/releases/v2.20.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.20.0 release notes 2 | ==================================== 3 | 4 | 5 | Nov 10, 2023 6 | ------------ 7 | 8 | This release brings some new features and improvements. 9 | 10 | 11 | New Features 12 | ^^^^^^^^^^^^ 13 | 14 | - **Constant values** 15 | 16 | Support for the ``const`` keyword. (:ref:`See docs `). 17 | 18 | - **Multiselect + Autocomplete** 19 | 20 | Support multiselect autocomplete input using ``"widget": "multiselect-autocomplete"``. 21 | 22 | - **Collapsible sections** 23 | 24 | You will now see ``[-]`` and ``[+]`` icons in section titles which can be used 25 | for collapsing and expanding array or object sections. 26 | 27 | 28 | Improvements 29 | ^^^^^^^^^^^^ 30 | 31 | - :issue:`121`: The form is more tolerant now when it receives ``null`` data for array or object types. 32 | The form will implicitly replace ``null`` with the appropriate blank data instead of throwing an error. 33 | 34 | 35 | react-json-form (JavaScript) 36 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 37 | 38 | `react-json-form `_ has been updated 39 | to version 2.12.0. 40 | -------------------------------------------------------------------------------- /docs/releases/v2.20.1.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.20.1 release notes 2 | ==================================== 3 | 4 | 5 | Nov 12, 2023 6 | ------------ 7 | 8 | This is a patch release. 9 | 10 | 11 | Bugfixes 12 | ^^^^^^^^ 13 | 14 | - Fixed Django version check logic. The previous release's check logic would 15 | throw and error for Django's alpha/beta versions. This release fixes that. 16 | - Improved schema type checking when multiple types are provided. 17 | -------------------------------------------------------------------------------- /docs/releases/v2.20.2.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.20.2 release notes 2 | ==================================== 3 | 4 | 5 | Nov 14, 2023 6 | ------------ 7 | 8 | This is a patch release which fixes a critical bug. 9 | 10 | 11 | Bugfixes 12 | ^^^^^^^^ 13 | 14 | - :issue:`130`: When initial data was null, the form was not automatically populating 15 | the appropriate data structure for it. 16 | -------------------------------------------------------------------------------- /docs/releases/v2.21.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.21.0 release notes 2 | ==================================== 3 | 4 | 5 | Nov 23, 2023 6 | ------------ 7 | 8 | This is a minor release. 9 | 10 | 11 | New Features 12 | ^^^^^^^^^^^^ 13 | 14 | - **Overriding references** 15 | 16 | While using ``$ref``, you can now override the properties set by the reference, 17 | like setting custom titles for the reference. 18 | 19 | 20 | Bugfixes 21 | ^^^^^^^^ 22 | 23 | - Earlier, when the schema didn't have a ``type`` at the top level but did have a ``$ref``, 24 | the form raised an error without considering the ``$ref``. Now, it will infer the type 25 | from ``$ref`` if ``type`` is not provided. 26 | 27 | 28 | react-json-form (JavaScript) 29 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 30 | 31 | `react-json-form `_ has been updated 32 | to version 2.13.0. 33 | -------------------------------------------------------------------------------- /docs/releases/v2.21.1.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.21.1 release notes 2 | ==================================== 3 | 4 | 5 | Dec 02, 2023 6 | ------------ 7 | 8 | This is a patch release which fixes an important bug in ``oneOf``/``anyOf`` subschema 9 | matching logic. 10 | 11 | 12 | Bugfixes 13 | ^^^^^^^^ 14 | 15 | - :issue:`131`: Previously, the form ignored ``oneOf``/``anyOf`` while syncing 16 | stale data with new schema. This caused the form to crash. This release fixes that. 17 | 18 | 19 | react-json-form (JavaScript) 20 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 21 | 22 | `react-json-form `_ has been updated 23 | to version 2.13.1. 24 | -------------------------------------------------------------------------------- /docs/releases/v2.21.2.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.21.2 release notes 2 | ==================================== 3 | 4 | 5 | Dec 02, 2023 6 | ------------ 7 | 8 | This is a patch release which fixes a mistake left in previous release. 9 | 10 | 11 | Bugfixes 12 | ^^^^^^^^ 13 | 14 | - Fixed a mistake introduced in the previous release related to subschema matching logic. 15 | 16 | 17 | react-json-form (JavaScript) 18 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 19 | 20 | `react-json-form `_ has been updated 21 | to version 2.13.2. 22 | -------------------------------------------------------------------------------- /docs/releases/v2.21.3.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.21.3 release notes 2 | ==================================== 3 | 4 | 5 | Jan 12, 2024 6 | ------------ 7 | 8 | This release brings some important bugfixes. 9 | 10 | 11 | Bugfixes 12 | ^^^^^^^^ 13 | 14 | - :issue:`132`: Added missing imports and also fixed errors in validators (by `Nicolas Forstner `__). 15 | - :issue:`135`: Fixed layout issues in DJango 4.x which caused the form to render very narrowly. 16 | - :issue:`136`: Fixed display title of selected choices of multiselect input. Earlier, selected choices displayed their value 17 | instead of the custom title. 18 | - :issue:`137`: Fixed JSON encoder errors when encoding UUID values in ``ArrayField`` (by `Andrés Reverón Molina `__). 19 | 20 | 21 | react-json-form (JavaScript) 22 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 23 | 24 | `react-json-form `_ has been updated 25 | to version 2.13.4. 26 | 27 | 28 | Thank you 29 | ^^^^^^^^^ 30 | 31 | Huge thanks to people who contributed code and reported these issues. 32 | -------------------------------------------------------------------------------- /docs/releases/v2.21.4.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.21.4 release notes 2 | ==================================== 3 | 4 | 5 | Jan 15, 2024 6 | ------------ 7 | 8 | This release fixes a bug introduced in the previous release. 9 | 10 | 11 | Bugfixes 12 | ^^^^^^^^ 13 | - Fixed a bug which caused the form to crash when using the ``autocomplete`` and 14 | ``multiselect-autocomplete`` widgets. 15 | 16 | 17 | react-json-form (JavaScript) 18 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 19 | 20 | `react-json-form `_ has been updated 21 | to version 2.13.5. 22 | -------------------------------------------------------------------------------- /docs/releases/v2.21.5.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.21.5 release notes 2 | ==================================== 3 | 4 | 5 | Jan 24, 2024 6 | ------------ 7 | 8 | This release fixes a bug introduced in the last couple of releases. 9 | 10 | 11 | Bugfixes 12 | ^^^^^^^^ 13 | 14 | - Added missing UUID import lack of which caused a ``NameError`` exception. 15 | -------------------------------------------------------------------------------- /docs/releases/v2.22.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.22.0 release notes 2 | ==================================== 3 | 4 | 5 | Feb 29, 2024 6 | ------------ 7 | 8 | This release brings some new features and a few bug fixes. 9 | 10 | 11 | New features 12 | ^^^^^^^^^^^^ 13 | 14 | - :pr:`142`: Improved support for UUID, datetime, Decimal, etc. types on ArrayField (by `Gyuri `__). 15 | - Support for ``uri`` and ``uri-reference`` formats. The input for this format will also display a clickable link to open 16 | the input's link value in new tab. 17 | - :issue:`156`: Support for clickable links for ``URLField`` inside ``ArrayField``. 18 | 19 | 20 | Bugfixes 21 | ^^^^^^^^ 22 | 23 | - Fixed dark mode CSS for Django 4 and 5. 24 | - :pr:`154`: Fixed typo in validation error message (by `Garret Heaton `__). 25 | 26 | 27 | react-json-form (JavaScript) 28 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 29 | 30 | `react-json-form `_ has been updated 31 | to version 2.14.0. 32 | -------------------------------------------------------------------------------- /docs/releases/v2.23.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.23.0 release notes 2 | ==================================== 3 | 4 | 5 | Sep 17, 2024 6 | ------------ 7 | 8 | This release fixes some bugs and brings a couple of new features. 9 | 10 | 11 | New features 12 | ^^^^^^^^^^^^ 13 | 14 | - :issue:`72`: ``ArrayField`` now accepts a custom schema. More in docs. :ref:`See usage docs `. 15 | 16 | - :pr:`162`: ``ArrayField``'s widget can be overridden now (by Willard Nilges). :ref:`See usage docs `. 17 | 18 | 19 | Bugfixes 20 | ^^^^^^^^ 21 | 22 | - :issue:`172`: Fixed a bug that prevented using ``JSONField`` as an item of the ``ArrayField``. 23 | 24 | - :pr:`175`: Fixed a bug which caused issues when choices had whitespace in them (by Kyle Perik). 25 | 26 | - :issue:`165`: Excluded the tests from PyPI wheel package (by Bruno Alla in PR :pr:`176`). 27 | 28 | 29 | Browser side improvements 30 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 31 | 32 | - Fixed (suppressed for the time being) buggy validation of ``oneOf``/``anyOf`` within an object. 33 | 34 | - Fixed crashes when using ``oneOf``. 35 | 36 | - Fixed number input validation. Some browsers need ``step=any`` for decimal values. 37 | 38 | 39 | react-json-form (JavaScript) 40 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 41 | 42 | `react-json-form `_ has been updated 43 | to version 2.14.2. 44 | -------------------------------------------------------------------------------- /docs/releases/v2.23.1.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.23.1 release notes 2 | ==================================== 3 | 4 | 5 | Oct 08, 2024 6 | ------------ 7 | 8 | 9 | Bugfixes 10 | ^^^^^^^^ 11 | 12 | - Fixed a bug related to in-browser validation for arrays with choices. 13 | 14 | 15 | react-json-form (JavaScript) 16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 17 | 18 | `react-json-form `_ has been updated 19 | to version 2.14.3. 20 | -------------------------------------------------------------------------------- /docs/releases/v2.23.2.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.23.2 release notes 2 | ==================================== 3 | 4 | 5 | Jan 30, 2025 6 | ------------ 7 | 8 | 9 | Bugfixes 10 | ^^^^^^^^ 11 | 12 | - :issue:`180`: Use ``step="1"`` for integer fields and ``step="any"`` for number fields. 13 | - Make anyOf/oneOf fields readonly in case the parent schema is also readonly (fix provided by (by `Jon Winsley `__). 14 | 15 | 16 | react-json-form (JavaScript) 17 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | `react-json-form `_ has been updated 20 | to version 2.14.4. 21 | -------------------------------------------------------------------------------- /docs/releases/v2.3.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.3.0 release notes 2 | =================================== 3 | 4 | 5 | Sep 22, 2021 6 | ------------ 7 | 8 | This version is fully backwards compatible with the previous version (v2.2.x). 9 | 10 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 11 | 12 | 13 | What's new 14 | ~~~~~~~~~~ 15 | 16 | react-json-form (JavaScript) 17 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | `react-json-form `_ has been updated 20 | to version 1.5.1. This version implements following new features and many 21 | bugfixes. 22 | 23 | Textarea input 24 | ^^^^^^^^^^^^^^ 25 | 26 | Now a ``textarea`` input can be specified for a string field using the ``widget`` 27 | keyword. 28 | 29 | Movable array items 30 | ^^^^^^^^^^^^^^^^^^^ 31 | 32 | Array items can now be re-ordered using arrow buttons. 33 | 34 | Bugfixes 35 | ^^^^^^^^ 36 | 37 | - Choices for boolean type will now return a boolean value. 38 | - Integer field's value was set as string before. Now it will be set as a numebr. 39 | - Initial blank data was not set for certain fields (booleans, integers). 40 | - Checkbox couldn't be unchecked once checked. 41 | 42 | Minor changes 43 | ^^^^^^^^^^^^^ 44 | 45 | - Small improvements to the widget's css 46 | - Javascript performance improvements 47 | -------------------------------------------------------------------------------- /docs/releases/v2.4.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.4.0 release notes 2 | =================================== 3 | 4 | 5 | Nov 08, 2021 6 | ------------ 7 | 8 | This version is fully backwards compatible with the previous version (v2.3.x). 9 | 10 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 11 | 12 | What's new 13 | ~~~~~~~~~~ 14 | 15 | react-json-form (JavaScript) 16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 17 | 18 | `react-json-form `_ has been updated 19 | to version 1.5.2. This version fixes the bug related to choice inputs. 20 | 21 | Bugfixes 22 | ^^^^^^^^ 23 | 24 | - Earlier version required ``psycopg2`` to be installed because of ``ArrayField``. 25 | Now, ``psycopg2`` isn't required unless ``ArrayField`` is used. 26 | - Choices for the choice input field were not set from the data. 27 | 28 | 29 | Minor changes 30 | ^^^^^^^^^^^^^ 31 | 32 | - Small improvements to the widget's css and icons 33 | -------------------------------------------------------------------------------- /docs/releases/v2.5.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.5.0 release notes 2 | =================================== 3 | 4 | 5 | Nov 19, 2021 6 | ------------ 7 | 8 | This version introduces a minor breaking change from the previous releases (2.4.x). 9 | 10 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 11 | 12 | What's new 13 | ~~~~~~~~~~ 14 | 15 | react-json-form (JavaScript) 16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 17 | 18 | `react-json-form `_ has been updated 19 | to version 1.6.0. 20 | 21 | Breaking Changes 22 | ^^^^^^^^^^^^^^^^ 23 | 24 | Earlier version used to set empty string (``''``) for blank number inputs. This 25 | version will use ``null`` instead. 26 | 27 | It will also change empty strings to ``null`` in the initial data as well. 28 | 29 | Bugfixes 30 | ^^^^^^^^ 31 | 32 | - Use ``null`` instead of empty string for blank number inputs. 33 | 34 | Minor changes 35 | ^^^^^^^^^^^^^ 36 | 37 | - Small improvements to the widget's css, such as the group panel's titles are 38 | now bolder. 39 | -------------------------------------------------------------------------------- /docs/releases/v2.6.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.6.0 release notes 2 | =================================== 3 | 4 | 5 | Dec 22, 2021 6 | ------------ 7 | 8 | This version introduces no breaking changes and is fully compatible with the 9 | previous releases (2.5.x). 10 | 11 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 12 | 13 | What's new 14 | ~~~~~~~~~~ 15 | 16 | New features 17 | ^^^^^^^^^^^^ 18 | 19 | - A new keyword called ``default`` can be used to specify default values for 20 | input fields. 21 | - A new keyword called ``readonly`` (alias ``readOnly``) can be used to make 22 | input fields readonly. 23 | 24 | react-json-form (JavaScript) 25 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 26 | 27 | `react-json-form `_ has been updated 28 | to version 1.7.1. 29 | -------------------------------------------------------------------------------- /docs/releases/v2.7.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.7.0 release notes 2 | =================================== 3 | 4 | 5 | Jan 31, 2022 6 | ------------ 7 | 8 | This version introduces no breaking changes and is fully compatible with the 9 | previous releases (2.6.x). 10 | 11 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 12 | 13 | What's new 14 | ~~~~~~~~~~ 15 | 16 | New features 17 | ^^^^^^^^^^^^ 18 | 19 | - Support for lazy translations within the schema. 20 | 21 | Bugfixes 22 | ^^^^^^^^ 23 | 24 | - Package didn't work as intended in Django 4.0 due to bad version checking. 25 | -------------------------------------------------------------------------------- /docs/releases/v2.8.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.8.0 release notes 2 | =================================== 3 | 4 | 5 | Feb 14, 2022 6 | ------------ 7 | 8 | This version introduces no breaking changes and is fully compatible with the 9 | previous releases (2.7.x). 10 | 11 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 12 | 13 | 14 | What's new 15 | ~~~~~~~~~~ 16 | 17 | 18 | New features 19 | ^^^^^^^^^^^^ 20 | 21 | - **Datetime input** 22 | 23 | Support for datetime input has been added for ``string`` type fields using 24 | ``format: 'datetime'`` key. 25 | Read about its usage here: :ref:`Datetime field`. 26 | 27 | - **Multiple choice selection** 28 | 29 | Support for multiple choice selection. Learn more in usage docs: :ref:`Multiple selections`. 30 | 31 | - **Default value for array** 32 | 33 | Support for ``default`` initial value for an array. Earlier, only array items 34 | could have a default value. The problem with that is when a new item was added, 35 | the same default value was also added again. 36 | 37 | With this feature, it is now possible to set multiple default values on an array. 38 | 39 | - **Clearable file inputs** 40 | 41 | Now file upload inputs can be cleared, i.e. their value can be unset. 42 | Earlier, it wasn't possible to empty a file input once a value was attached. 43 | 44 | - **Callable schema may receive the model instance** 45 | 46 | Now the callable schema function may optionally receive the current model instance as an argument. 47 | 48 | 49 | Bug fixes 50 | ^^^^^^^^^ 51 | 52 | - :issue:`25`: Fixed a bug where editing a key in an extendable dict (object) made the key disappear. 53 | - :issue:`27`: Fixed a bug to make the editor work in admin inlines. 54 | 55 | 56 | Minor improvements 57 | ^^^^^^^^^^^^^^^^^^ 58 | 59 | - **Autogrowing textarea** 60 | 61 | Textarea's height will now grow automatically as the user types in. This saves 62 | the user from having to manually resize the textarea. 63 | 64 | - **Animated list items** 65 | 66 | List items will have a nice animation when moved up/down or removed. This will 67 | help provide some feedback to the user. Earlier it wasn't obvious when list items 68 | were moved. 69 | 70 | 71 | react-json-form (JavaScript) 72 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 73 | 74 | `react-json-form `_ has been updated 75 | to version 1.9.0. 76 | -------------------------------------------------------------------------------- /docs/releases/v2.8.1.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.8.1 release notes 2 | =================================== 3 | 4 | 5 | Mar 13, 2022 6 | ------------ 7 | 8 | This is a minor release and is fully backwards compatible. 9 | 10 | 11 | What's new 12 | ~~~~~~~~~~ 13 | 14 | 15 | New features 16 | ^^^^^^^^^^^^ 17 | 18 | - Ask for confirmation before clearing file input 19 | 20 | 21 | Bug fixes 22 | ^^^^^^^^^ 23 | 24 | - :issue:`28`: Fixed a bug to make the "Clear" file button visible when filename was too long. 25 | 26 | 27 | react-json-form (JavaScript) 28 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 29 | 30 | `react-json-form `_ has been updated 31 | to version 1.10.0. 32 | -------------------------------------------------------------------------------- /docs/releases/v2.9.0.rst: -------------------------------------------------------------------------------- 1 | django-jsonform 2.9.0 release notes 2 | =================================== 3 | 4 | 5 | Apr 22, 2022 6 | ------------ 7 | 8 | This version introduces some :ref:`backwards-incompatible changes ` with the previous versions 9 | related to CSS styling and internal HTML structure . 10 | 11 | Like the previous version, it also supports ``Django >= 2.0`` and ``Python >= 3.4``. 12 | 13 | What's new 14 | ~~~~~~~~~~ 15 | 16 | 17 | New features 18 | ^^^^^^^^^^^^ 19 | 20 | - Support for displaying help text under inputs (using the ``help_text`` or ``helpText`` keyword). 21 | - New :doc:`template filters ` to help convert datetime strings into datetime objects in templates. 22 | - Improvements to CSS to ensure compatibility with the Django-Grappelli admin. 23 | 24 | 25 | Bug fixes 26 | ^^^^^^^^^ 27 | 28 | - :issue:`34`: Fixed a CSS stylings for Djang-Grappelli admin. 29 | - :issue:`35`: Fixed a bug to reset file input when the file is cleared. 30 | 31 | 32 | react-json-form (JavaScript) 33 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 34 | 35 | `react-json-form `_ has been updated 36 | to version 1.11.0. 37 | 38 | 39 | .. _breaking-changes-2-9-0: 40 | 41 | Breaking changes 42 | ~~~~~~~~~~~~~~~~ 43 | 44 | The internal HTML structure of the widget, i.e. how the the fields are rendered, 45 | has been changed. Due to this, some of the CSS code has also been changed. 46 | 47 | You don't need to worry about this unless you've written custom CSS styles for the 48 | widget. If so, this is something to beware for. 49 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==3.5.2 2 | sphinx-rtd-theme==0.5.2 3 | Jinja2<3.1 4 | -------------------------------------------------------------------------------- /docs/settings.rst: -------------------------------------------------------------------------------- 1 | Settings 2 | ======== 3 | 4 | This documents lists the settings which can be used to configure django-jsonform's 5 | behaviour. 6 | 7 | .. setting:: DJANGO_JSONFORM 8 | 9 | ``DJANGO_JSONFORM`` 10 | ------------------- 11 | 12 | .. versionadded:: 2.11 13 | 14 | This is the main "wrapper" setting to hold all django-jsonform specific settings. 15 | 16 | Defaults: 17 | 18 | .. code-block:: python 19 | 20 | DJANGO_JSONFORM = { 21 | 'FILE_HANDLER': '' 22 | } 23 | 24 | 25 | All the following documented settings are keys of the main :setting:`DJANGO_JSONFORM` 26 | setting dict. 27 | 28 | 29 | .. setting:: FILE_HANDLER 30 | 31 | ``FILE_HANDLER`` 32 | ~~~~~~~~~~~~~~~~ 33 | 34 | Default: ``''`` (Empty string) 35 | 36 | URL to the file handler view. Example: ``'/json-file-handler/'``. 37 | 38 | Use this setting to declare a common file handler function for all ``JSONField`` instances. 39 | All the file upload and listing requests will be sent to this URL. 40 | 41 | ---- 42 | 43 | ``JSONFORM_UPLOAD_HANDLER`` 44 | --------------------------- 45 | 46 | .. deprecated:: 2.11 47 | 48 | This setting was used for declaring the file upload handler function. 49 | 50 | It is only kept for backwards compatibility. It will be removed in future. 51 | -------------------------------------------------------------------------------- /docs/templatetags.rst: -------------------------------------------------------------------------------- 1 | Template tags and filters 2 | ========================= 3 | 4 | .. module:: django_jsonform.templatetags.django_jsonform 5 | :synopsis: Template tags and filters 6 | 7 | 8 | django-jsonform provides some useful filters for working with json data in the 9 | templates. 10 | 11 | Usage 12 | ----- 13 | 14 | To use the filters and tags, you'll have to first load them in a template: 15 | 16 | .. code-block:: html 17 | 18 | 19 | 20 | {% load django_jsonform %} 21 | 22 | 23 | 24 | Available filters 25 | ----------------- 26 | 27 | .. templatefilter:: parse_datetime 28 | 29 | ``parse_datetime`` 30 | ~~~~~~~~~~~~~~~~~~ 31 | 32 | .. versionadded:: 2.9 33 | 34 | This filter converts a date string (``'YYYY-MM-DD'``) or a datetime string in ISO format 35 | to Python's ``datetime.datetime`` object. 36 | 37 | django-jsonform keeps the datetime as ISO string in the database. But in templates, 38 | you most probably would like to display the date in a nice, user-friendly format. 39 | 40 | Use this filter to convert the string to a ``datetime`` object, and Django will 41 | automatically format the date. 42 | 43 | Usage: 44 | 45 | .. code-block:: html 46 | 47 | {{ date_string | parse_datetime }} 48 | 49 | 50 | {{ date_string | parse_datetime | date:'d M, Y' }} 51 | 52 | .. templatefilter:: parse_time 53 | 54 | ``parse_time`` 55 | ~~~~~~~~~~~~~~ 56 | 57 | .. versionadded:: 2.9 58 | 59 | This filter converts a time string (24-hour ``'HH:MM:SS'``) to Python's 60 | ``datetime.time`` object. 61 | 62 | Usage: 63 | 64 | .. code-block:: html 65 | 66 | {{ time_string | parse_time }} 67 | 68 | 69 | {{ time_string | parse_time | time:'H:i a' }} 70 | -------------------------------------------------------------------------------- /docs/troubleshooting.rst: -------------------------------------------------------------------------------- 1 | Troubleshooting 2 | =============== 3 | 4 | This page contains tips and advice about some common errors and problems encountered 5 | with django-jsonform. 6 | 7 | 8 | Schema and data structure do not match 9 | -------------------------------------- 10 | 11 | This error occurs when the schema doesn't match the data structure of the JSON. 12 | 13 | Some possible causes of this error: 14 | 15 | 16 | 1. **Changing schema for a field** 17 | 18 | If you've changed the schema for an existing field, then the current JSON 19 | data saved in that field would not match the new schema. So, you'll also need 20 | to "migrate" the old JSON data to fit the newly written schema. 21 | Currently, there is no automatic mechanism for migrating JSON data when the schema changes. 22 | You'll have to do this manually from a Django shell. 23 | 24 | 2. **Defining schema for existing data** 25 | 26 | If you're defining a schema for existing data, please ensure that the data 27 | conforms to the schema. 28 | If the data is too complex and unstructured, it might be difficult to write 29 | a schema for that. In that case, you should first try to simplify the data. -------------------------------------------------------------------------------- /docs/utils.rst: -------------------------------------------------------------------------------- 1 | Utilities 2 | ========= 3 | 4 | .. module:: django_jsonform.utils 5 | :synopsis: Helper functions and classes 6 | 7 | Helper functions and classes. 8 | 9 | 10 | ``ErrorMap`` 11 | ------------ 12 | 13 | .. class:: ErrorMap() 14 | 15 | .. versionadded:: 2.15 16 | 17 | It is basically a subclass of ``dict`` but it makes it easier to create an error 18 | mapping for the widget. 19 | 20 | **Methods** 21 | 22 | .. method:: set(coords, msg) 23 | 24 | Set the given message for the given coordinates. 25 | 26 | ``coords`` - A ``list`` of coordinates of the field. 27 | 28 | ``msg`` - A ``string`` or a ``list`` of error messages. 29 | 30 | .. method:: append(coords, msg) 31 | 32 | Append the given message to previously added coordinates. If the coordinates 33 | don't exit, it acts like the ``set()`` method. 34 | 35 | 36 | ``coords`` - A ``list`` of coordinates of the field. 37 | 38 | ``msg`` - A ``string`` or a ``list`` of error messages. 39 | 40 | Usage: 41 | 42 | .. code-block:: 43 | 44 | from django_jsonform.utils import ErrorMap 45 | 46 | error_map = ErrorMap() 47 | 48 | # set an error message 49 | error_map.set(coords=[0], msg='This value is invalid') 50 | 51 | # append error messages on same field 52 | error_map.append(coords=[0], msg='Second error message') 53 | 54 | print(error_map) 55 | 56 | {'0': ['This value is invalid', 'Second error message']} 57 | 58 | 59 | ``join_coords`` 60 | --------------- 61 | 62 | .. function:: join_coords(*coords) 63 | 64 | .. versionadded:: 2.15 65 | 66 | Generates a string by joining the given coordinates. 67 | 68 | Internally, we use the section sign (``§``) for joining the coordinates. Earlier, 69 | a hyphen (``-``) was used, but that caused some complications when a key in a 70 | schema (i.e. a field name) had a hyphen in it as it was impossible to know whether the 71 | hyphen was the separator or part of the key. 72 | 73 | Now, this symbol is chosen because it's very rarely used. 74 | 75 | .. code-block:: 76 | 77 | from django_jsonform.utils import join_coords 78 | 79 | join_coords('person', 0, 'name') # -> 'person§0§name' 80 | 81 | 82 | ``split_coords`` 83 | ---------------- 84 | 85 | .. function:: split_coords(coords) 86 | 87 | .. versionadded:: 2.15 88 | 89 | Splits a coordinates string into individual coordinates. 90 | 91 | The section sign (``§``) is used for splitting the coordinates. 92 | 93 | .. code-block:: 94 | 95 | from django_jsonform.utils import split_coords 96 | 97 | split_coords('person§0§name') # -> ['person', '0', 'name'] 98 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = django-jsonform 3 | version = attr: django_jsonform.__version__ 4 | description = A user-friendly JSON editing form for Django admin. 5 | long_description = file: README.md 6 | long_description_content_type = text/markdown 7 | url = https://www.github.com/bhch/django-jsonform 8 | author = Bharat Chauhan 9 | author_email = tell.bhch@gmail.com 10 | license = BSD-3-Clause 11 | classifiers = 12 | Environment :: Web Environment 13 | Framework :: Django 14 | Intended Audience :: Developers 15 | License :: OSI Approved :: BSD License 16 | Operating System :: OS Independent 17 | Programming Language :: Python 18 | Programming Language :: Python :: 3 19 | Programming Language :: Python :: 3 :: Only 20 | Programming Language :: Python :: 3.4 21 | Programming Language :: Python :: 3.5 22 | Programming Language :: Python :: 3.6 23 | Programming Language :: Python :: 3.7 24 | Programming Language :: Python :: 3.8 25 | Programming Language :: Python :: 3.9 26 | Programming Language :: Python :: 3.10 27 | Topic :: Internet :: WWW/HTTP 28 | Topic :: Internet :: WWW/HTTP :: Dynamic Content 29 | 30 | [options] 31 | python_requires = >= 3.4 32 | install_requires = django >= 2.0 33 | packages = find: 34 | include_package_data = true 35 | zip_safe = false 36 | 37 | [options.packages.find] 38 | exclude = 39 | tests 40 | tests.* 41 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup() 5 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhch/django-jsonform/e485ef149902962a5b9ec83bcb1150ffbee64ef6/tests/__init__.py -------------------------------------------------------------------------------- /tests/__main__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | import django 5 | 6 | 7 | TEST_DIR = os.path.dirname(os.path.abspath(__file__)) 8 | 9 | 10 | if __name__ == '__main__': 11 | os.environ['DJANGO_SETTINGS_MODULE'] = 'django_settings' 12 | django.setup() 13 | loader = unittest.TestLoader() 14 | if len(sys.argv) > 1: 15 | suite = loader.loadTestsFromName(sys.argv[1]) 16 | else: 17 | suite = loader.discover(TEST_DIR) 18 | runner = unittest.TextTestRunner() 19 | runner.run(suite) 20 | -------------------------------------------------------------------------------- /tests/django_settings.py: -------------------------------------------------------------------------------- 1 | SECRET_KEY = "secret for test" 2 | 3 | ROOT_URLCONF='django_jsonform.urls' 4 | 5 | INSTALLED_APPS=[ 6 | 'django_jsonform', 7 | ] 8 | 9 | DATABASES = { 10 | 'default': { 11 | 'ENGINE': 'django.db.backends.sqlite3', 12 | 'NAME': ":memory:", 13 | }, 14 | } 15 | 16 | TEMPLATES = [ 17 | { 18 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 19 | 'DIRS': [], 20 | 'APP_DIRS': True, 21 | 'OPTIONS': { 22 | 'context_processors': [ 23 | 'django.template.context_processors.debug', 24 | 'django.template.context_processors.request', 25 | 'django.contrib.auth.context_processors.auth', 26 | 'django.contrib.messages.context_processors.messages', 27 | ], 28 | }, 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /tests/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhch/django-jsonform/e485ef149902962a5b9ec83bcb1150ffbee64ef6/tests/models/__init__.py -------------------------------------------------------------------------------- /tests/models/test_fields.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from unittest.mock import MagicMock, patch 3 | from django_jsonform.models.fields import JSONField, ArrayField 4 | from django.db import models 5 | 6 | 7 | class JSONFieldTests(TestCase): 8 | def test_calls_pre_save_hook_if_provided(self): 9 | pre_save_hook = MagicMock(return_value={}) 10 | field = JSONField(schema={}, pre_save_hook=pre_save_hook) 11 | field.attname = 'test' 12 | model_instance = MagicMock() 13 | field.pre_save(model_instance, True) 14 | pre_save_hook.assert_called_once() 15 | 16 | 17 | class ArrayFieldTests(TestCase): 18 | def test_allows_providing_custom_schema(self): 19 | schema = {'type': 'array', 'items': {'type': 'string'}} 20 | 21 | # initialising the ArrayField with custom schema must succeed 22 | field = ArrayField(models.CharField(max_length=10), schema=schema) 23 | -------------------------------------------------------------------------------- /tests/test_templatetags.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from datetime import datetime, date, time 3 | from django_jsonform.templatetags.django_jsonform import parse_datetime, parse_time 4 | 5 | 6 | class ParseDatetimeFilterTests(TestCase): 7 | def test_datetime_string_is_converted_to_datetime_object(self): 8 | now = datetime.now() 9 | now_iso = now.isoformat() 10 | 11 | parsed = parse_datetime(now_iso) 12 | 13 | self.assertTrue(isinstance(parsed, datetime)) 14 | self.assertEqual(parsed, now) 15 | 16 | 17 | def test_date_string_is_converted_to_datetime_object(self): 18 | self.assertTrue(isinstance(parse_datetime('2022-04-21'), datetime)) 19 | 20 | 21 | class ParseTimeFilterTests(TestCase): 22 | def test_time_string_is_converted_to_time_object(self): 23 | self.assertTrue(isinstance(parse_time('10:10:00'), time)) 24 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from django_jsonform.utils import (normalize_schema, join_coords, split_coords, 3 | ErrorMap) 4 | from django_jsonform.constants import JOIN_SYMBOL 5 | 6 | 7 | class TestNormalizeSchemaFunction(TestCase): 8 | """Tests for utils.normalize_schema function""" 9 | 10 | def test_normalized_schema_is_same(self): 11 | """Normalized schema must be the same as input schema 12 | if there are no python objects in the schema. 13 | """ 14 | schema = { 15 | 'type': 'dict', 16 | 'keys': { 17 | 'name': { 18 | 'type': 'string', 19 | }, 20 | 'wishlist': { 21 | 'type': 'array', 22 | 'items': { 23 | 'type': 'string', 24 | } 25 | } 26 | } 27 | } 28 | 29 | self.assertEqual(schema, normalize_schema(schema)) 30 | 31 | 32 | class TestJoinCoordsFunction(TestCase): 33 | """Tests for join_coords function""" 34 | 35 | def test_with_one_coord(self): 36 | self.assertEqual('0', join_coords('0')) 37 | 38 | def test_with_accepts_integers(self): 39 | self.assertEqual('0', join_coords(0)) 40 | 41 | def test_with_multiple_arguments(self): 42 | self.assertEqual('0%sname' % JOIN_SYMBOL, join_coords(0, 'name')) 43 | 44 | def test_strips_extra_join_symbol(self): 45 | self.assertEqual( 46 | '0%sname' % JOIN_SYMBOL, 47 | join_coords(0, 'name', JOIN_SYMBOL) 48 | ) 49 | 50 | 51 | class TestSplitCoordsFunction(TestCase): 52 | """Tests for split_coords function""" 53 | 54 | def test_splits_at_join_symbol(self): 55 | self.assertEqual(split_coords('a%sb' % JOIN_SYMBOL), ['a', 'b']) 56 | 57 | 58 | class TestErrorMapClass(TestCase): 59 | """Tests for ErrorMap class""" 60 | 61 | def test_set_method(self): 62 | error_map = ErrorMap() 63 | error_map.set(coords=[0, 'name'], msg='Error') 64 | self.assertEqual(error_map, {join_coords(0, 'name'): ['Error']}) 65 | 66 | def test_append_method(self): 67 | error_map = ErrorMap() 68 | 69 | # 1. append must add a new key if not present 70 | error_map.append(coords=[0, 'name'], msg='Error 1') 71 | self.assertEqual(error_map, {join_coords(0, 'name'): ['Error 1']}) 72 | 73 | # 2. append must add a new key if not present 74 | error_map.append(coords=[0, 'name'], msg='Error 2') 75 | self.assertEqual( 76 | error_map, 77 | {join_coords(0, 'name'): ['Error 1', 'Error 2']} 78 | ) 79 | -------------------------------------------------------------------------------- /tests/test_widgets.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from unittest.mock import MagicMock 3 | from django_jsonform.widgets import JSONFormWidget 4 | 5 | 6 | class JSONFormWidgetTests(TestCase): 7 | def test_passes_model_instance_to_schema_callable(self): 8 | """If an 'instance' attribute was set on the widget, 9 | it should be passed to the schema function in the 10 | widget's render method. 11 | """ 12 | 13 | schema_func = MagicMock(return_value={}) 14 | 15 | widget = JSONFormWidget(schema=schema_func) 16 | widget.render(name='test', value='') 17 | # no 'instance' attribute set, 18 | # nothing should be passed to callable 19 | schema_func.assert_called_with() 20 | 21 | widget.instance = 1 # set instance 22 | widget.render(name='test', value='') 23 | # 'instance' attribute set, 24 | # must be passed to callable 25 | schema_func.assert_called_with(1) 26 | 27 | def test_instance_arg_is_conditionally_passed_to_schema_callable(self): 28 | """The 'instance' argument must be conditionally passed 29 | to the schema function, i.e. only if it accepts arguments. 30 | This is to ensure backwards compatibility. 31 | """ 32 | 33 | schema_func = lambda: {} # accepts no args 34 | 35 | widget = JSONFormWidget(schema=schema_func) 36 | widget.instance = 1 37 | # must not raise any exceptions 38 | widget.render(name='test', value='') 39 | 40 | def test_merges_error_maps(self): 41 | """error_map must be merged with the previously passed error_maps""" 42 | widget = JSONFormWidget(schema={}) 43 | 44 | error_map = {'0': 'First error'} 45 | widget.add_error(error_map) 46 | 47 | error_map_2 = {'0': 'Second error'} 48 | widget.add_error(error_map_2) 49 | 50 | error_map_3 = {'0': ['Third error']} # if messages are in array 51 | widget.add_error(error_map_3) 52 | 53 | self.assertEqual( 54 | widget.error_map['0'], 55 | ['First error', 'Second error', 'Third error'] 56 | ) 57 | --------------------------------------------------------------------------------