Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
D
django-forms-as-jsonschema
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Model registry
Monitor
Service Desk
Analyze
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
AlekSIS®
Libraries
django-forms-as-jsonschema
Compare revisions
237dda613375bf34363c0aea674077bd68aa4d6e to c336d4d3831e489e000fb301abd5bca6652e86c8
Compare revisions
Changes are shown as if the
source
revision was being merged into the
target
revision.
Learn more about comparing revisions.
Source
AlekSIS/libs/django-forms-as-jsonschema
Select target project
No results found
c336d4d3831e489e000fb301abd5bca6652e86c8
Select Git revision
Swap
Target
AlekSIS/libs/django-forms-as-jsonschema
Select target project
AlekSIS/libs/django-forms-as-jsonschema
1 result
237dda613375bf34363c0aea674077bd68aa4d6e
Select Git revision
Show changes
Only incoming changes from source
Include changes to target since source was created
Compare
Commits on Source (2)
Document layout.py with some additional fixes
· 54cebe0b
Nik | Klampfradler
authored
2 years ago
54cebe0b
Document jsonschema.py with some fixes
· c336d4d3
Nik | Klampfradler
authored
2 years ago
c336d4d3
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
django_forms_as_jsonschema/jsonschema.py
+34
-55
34 additions, 55 deletions
django_forms_as_jsonschema/jsonschema.py
django_forms_as_jsonschema/layout.py
+30
-14
30 additions, 14 deletions
django_forms_as_jsonschema/layout.py
with
64 additions
and
69 deletions
django_forms_as_jsonschema/jsonschema.py
View file @
c336d4d3
from
typing
import
Optional
from
django
import
forms
from
django_forms_as_jsonschema
.layout
import
_Section
from
.layout
import
_Section
class
JSONSchema
:
"""
Encapsulation of a JSON form schema.
"""
def
__init__
(
self
):
self
.
schema
=
{
...
...
@@ -14,6 +17,8 @@ class JSONSchema:
@staticmethod
def
generate_field
(
field
):
"""
Generate schema for one form field.
"""
# Defaults for a generic field
new_field
=
{
"
type
"
:
"
string
"
,
"
title
"
:
str
(
field
.
label
or
""
),
...
...
@@ -22,63 +27,43 @@ class JSONSchema:
"
required
"
:
field
.
required
,
}
# string, number, integer, boolean.
if
type
(
field
.
widget
)
==
forms
.
TextInput
:
# Inspect field and update schema accordingly
if
isinstance
(
field
.
widget
,
forms
.
TextInput
):
new_field
[
"
type
"
]
=
"
string
"
elif
type
(
field
.
widget
)
==
forms
.
NumberInput
:
new_field
[
"
type
"
]
=
"
integer
"
if
type
(
field
)
==
forms
.
IntegerField
else
"
number
"
elif
typ
e
(
field
.
widget
)
==
forms
.
EmailInput
:
elif
isinstance
(
field
.
widget
,
forms
.
NumberInput
)
and
isinstance
(
field
,
forms
.
IntegerField
):
new_field
[
"
type
"
]
=
"
integer
"
elif
isinstance
(
field
.
widget
,
forms
.
NumberInput
):
new_field
[
"
type
"
]
=
"
number
"
elif
isinstanc
e
(
field
.
widget
,
forms
.
EmailInput
)
:
new_field
[
"
type
"
]
=
"
string
"
new_field
[
"
format
"
]
=
"
email
"
elif
type
(
field
.
widget
)
==
forms
.
URLInput
:
elif
isinstance
(
field
.
widget
,
forms
.
URLInput
):
new_field
[
"
type
"
]
=
"
string
"
new_field
[
"
format
"
]
=
"
url
"
elif
type
(
field
.
widget
)
==
forms
.
PasswordInput
:
elif
isinstance
(
field
.
widget
,
forms
.
PasswordInput
):
new_field
[
"
type
"
]
=
"
string
"
new_field
[
"
format
"
]
=
"
password
"
new_field
[
"
x-display
"
]
=
"
password
"
elif
type
(
field
.
widget
)
==
forms
.
HiddenInput
:
elif
isinstance
(
field
.
widget
,
forms
.
HiddenInput
):
new_field
[
"
type
"
]
=
"
string
"
new_field
[
"
format
"
]
=
"
hidden
"
elif
type
(
field
.
widget
)
==
forms
.
MultipleHiddenInput
:
...
elif
type
(
field
.
widget
)
in
[
forms
.
FileInput
,
forms
.
ClearableFileInput
]:
new_field
|=
{
"
type
"
:
"
string
"
,
"
contentMediaType
"
:
"
image/*
"
if
type
(
field
)
==
forms
.
ImageField
else
"
*
"
,
"
writeOnly
"
:
True
}
# Fixme: differentiate between clearable and non-clearable
# elif type(field.widget) == forms.ClearableFileInput:
# ...
elif
type
(
field
.
widget
)
==
forms
.
Textarea
:
elif
isinstance
(
field
.
widget
,
forms
.
FileInput
):
new_field
[
"
type
"
]
=
"
string
"
new_field
[
"
contentMediaType
"
]
=
"
image/*
"
if
isinstance
(
field
,
forms
.
ImageField
)
else
"
*
"
new_field
[
"
writeOnly
"
]
=
True
elif
isinstance
(
field
.
widget
,
forms
.
Textarea
):
new_field
[
"
x-display
"
]
=
"
textarea
"
elif
type
(
field
.
widget
)
==
forms
.
DateInput
:
elif
isinstance
(
field
.
widget
,
forms
.
DateInput
):
new_field
[
"
type
"
]
=
"
string
"
new_field
[
"
format
"
]
=
"
date
"
elif
type
(
field
.
widget
)
==
forms
.
DateTimeInput
:
elif
isinstance
(
field
.
widget
,
forms
.
DateTimeInput
):
new_field
[
"
type
"
]
=
"
string
"
new_field
[
"
format
"
]
=
"
date-time
"
elif
type
(
field
.
widget
)
==
forms
.
TimeInput
:
elif
isinstance
(
field
.
widget
,
forms
.
TimeInput
):
new_field
[
"
type
"
]
=
"
string
"
new_field
[
"
format
"
]
=
"
time
"
elif
type
(
field
.
widget
)
==
forms
.
CheckboxInput
:
elif
isinstance
(
field
.
widget
,
forms
.
CheckboxInput
):
new_field
[
"
type
"
]
=
"
boolean
"
elif
type
(
field
.
widget
)
in
[
forms
.
Select
,
forms
.
SelectMultiple
,
forms
.
RadioSelect
,
forms
.
CheckboxSelectMultiple
,
forms
.
NullBooleanSelect
]:
one_of
=
[]
...
...
@@ -95,26 +80,17 @@ class JSONSchema:
new_field
[
"
type
"
]
=
"
string
"
new_field
[
"
oneOf
"
]
=
one_of
if
typ
e
(
field
.
widget
)
==
forms
.
RadioSelect
:
if
isinstanc
e
(
field
.
widget
,
forms
.
RadioSelect
)
:
new_field
[
"
x-display
"
]
=
"
radio
"
elif
typ
e
(
field
.
widget
)
==
forms
.
CheckboxSelectMultiple
:
elif
isinstanc
e
(
field
.
widget
,
forms
.
CheckboxSelectMultiple
)
:
new_field
[
"
x-display
"
]
=
"
checkbox
"
elif
type
(
field
.
widget
)
==
forms
.
SplitDateTimeWidget
:
...
elif
type
(
field
.
widget
)
==
forms
.
SplitHiddenDateTimeWidget
:
...
elif
type
(
field
.
widget
)
==
forms
.
SelectDateWidget
:
...
else
:
print
(
"
[Django-forms-as-jsonschema] Unsupported field/widget detected:
"
)
print
(
f
"
{
field
=
}
,
{
type
(
field
)
=
}
,
{
type
(
field
.
widget
)
=
}
"
)
raise
TypeError
(
f
"
Unsupported field/widget:
{
repr
(
field
)
}
,
{
repr
(
field
.
widget
)
}
"
)
return
new_field
def
add_field
(
self
,
name
,
field
,
metadata
:
dict
=
None
):
def
add_field
(
self
,
name
,
field
,
metadata
:
Optional
[
dict
]
=
None
):
"""
Add a form field to this schema.
"""
new_field
=
self
.
generate_field
(
field
)
if
metadata
and
metadata
.
get
(
"
section_name
"
)
and
self
.
schema
[
"
properties
"
].
get
(
metadata
[
"
section_name
"
]
...
...
@@ -127,6 +103,7 @@ class JSONSchema:
@staticmethod
def
generate_section
(
section
:
_Section
):
"""
Generate schema for a form section.
"""
sec
=
{
"
title
"
:
section
.
title
,
"
type
"
:
"
object
"
,
...
...
@@ -139,7 +116,9 @@ class JSONSchema:
return
sec
def
add_section
(
self
,
section
:
_Section
):
"""
Add a form section to this schema.
"""
self
.
schema
[
"
properties
"
][
section
.
codename
]
=
self
.
generate_section
(
section
)
def
update_properties
(
self
,
props
:
dict
):
"""
Merge new property values into schema properties.
"""
self
.
schema
[
"
properties
"
].
update
(
props
)
This diff is collapsed.
Click to expand it.
django_forms_as_jsonschema/layout.py
View file @
c336d4d3
import
uuid
"""
Re-implementation of django-material
'
s form layout mechanics.
This implementation provides a drop-in replacement for django-material
'
s
Layout, Row, and Fieldset classes, which allow rendering into a JSON schema.
cf. http://docs.viewflow.io/material_forms.html
"""
from
abc
import
ABC
from
dataclasses
import
dataclass
from
typing
import
Union
from
typing
import
Optional
,
Union
from
django.utils.text
import
slugify
class
LayoutNode
(
ABC
):
"""
Abstract node in a form layout.
"""
def
__init__
(
self
,
*
elements
):
self
.
elements
=
elements
def
build_schema
(
self
,
schema
,
form_fields
)
->
dict
:
"""
Render this node into a JSON schema fragment.
"""
props
=
{}
for
field
in
self
.
elements
:
if
isinstance
(
field
,
LayoutNode
):
# Recurse and add fragment of the sub-node
built_schema
=
field
.
build_schema
(
schema
,
form_fields
)
props
.
update
(
built_schema
[
"
properties
"
])
else
:
# Add verbatim field
props
[
field
]
=
schema
.
generate_field
(
form_fields
[
field
])
return
{
"
properties
"
:
props
}
@dataclass
class
_Section
:
"""
Visual section in a form.
"""
codename
:
str
title
:
str
description
:
str
=
None
description
:
Optional
[
str
]
=
None
class
Row
(
LayoutNode
):
"""
Visual row in a form.
"""
def
build_schema
(
self
,
schema
,
form_fields
):
row_name
=
"
row-
"
+
str
(
uuid
.
uuid4
())
"""
Render this row as a JSON schema fragment.
"""
row_name
=
f
"
row-
{
id
(
self
)
}
"
fields
=
super
().
build_schema
(
schema
,
form_fields
)[
"
properties
"
]
for
k
in
fields
.
keys
():
fields
[
k
][
"
x-options
"
]
=
{
"
fieldColProps
"
:
{
"
cols
"
:
12
,
"
md
"
:
""
}
}
# Make row responsive by extending each field to full width on smaller viewports
fields
[
k
].
setdefault
(
"
x-options
"
,
{}).
setdefault
(
"
fieldColProps
"
,
{}).
update
({
"
cols
"
:
12
,
"
md
"
:
""
})
return
{
"
properties
"
:
{
row_name
:
{
...
...
@@ -50,15 +63,18 @@ class Row(LayoutNode):
class
Fieldset
(
LayoutNode
):
"""
Visual set of fields in a form.
"""
def
__init__
(
self
,
name
:
Union
[
tuple
[
str
,
str
],
str
],
*
elements
):
super
().
__init__
(
*
elements
)
codename
=
str
(
slugify
(
name
))
if
type
(
name
)
==
tuple
:
self
.
section
=
_Section
(
codename
,
*
map
(
str
,
name
))
if
isinstance
(
name
,
tuple
):
self
.
section
=
_Section
(
codename
,
*
name
)
else
:
self
.
section
=
_Section
(
codename
,
str
(
name
)
)
self
.
section
=
_Section
(
codename
,
name
)
def
build_schema
(
self
,
schema
,
form_fields
)
->
dict
:
"""
Render this fieldset as a JSON schema fragment.
"""
section
=
schema
.
generate_section
(
self
.
section
)
section
[
"
properties
"
].
update
(
super
().
build_schema
(
schema
,
form_fields
)[
"
properties
"
])
return
{
...
...
@@ -69,4 +85,4 @@ class Fieldset(LayoutNode):
class
Layout
(
LayoutNode
):
...
"""
Full form layout as the root node.
"""
This diff is collapsed.
Click to expand it.