├── .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 |
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 | 
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 |
15 |
--------------------------------------------------------------------------------
/django_jsonform/static/django_jsonform/img/collapsed-indicator-bg-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/django_jsonform/static/django_jsonform/img/collapsed-indicator-bg.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/django_jsonform/static/django_jsonform/img/control-icons.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/django_jsonform/static/django_jsonform/img/icon-addlink.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/django_jsonform/static/django_jsonform/img/icon-changelink.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/django_jsonform/static/django_jsonform/img/icon-deletelink.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------