dynamicforms 0.76.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dynamicforms/__init__.py +8 -0
- dynamicforms/action.py +521 -0
- dynamicforms/apps.py +5 -0
- dynamicforms/fields.py +1570 -0
- dynamicforms/filters.py +44 -0
- dynamicforms/locale/en/LC_MESSAGES/django.po +67 -0
- dynamicforms/locale/en/LC_MESSAGES/djangojs.po +31 -0
- dynamicforms/locale/sl/LC_MESSAGES/django.po +71 -0
- dynamicforms/locale/sl/LC_MESSAGES/djangojs.po +36 -0
- dynamicforms/management/__init__.py +0 -0
- dynamicforms/management/commands/__init__.py +0 -0
- dynamicforms/management/commands/clean_dfpreuploaded_files.py +26 -0
- dynamicforms/migrations/__init__.py +0 -0
- dynamicforms/mixins/__init__.py +27 -0
- dynamicforms/mixins/action.py +14 -0
- dynamicforms/mixins/choice/__init__.py +94 -0
- dynamicforms/mixins/choice/allow_tags.py +79 -0
- dynamicforms/mixins/choice/null_choice.py +26 -0
- dynamicforms/mixins/choice/single_choice.py +21 -0
- dynamicforms/mixins/color_field.py +4 -0
- dynamicforms/mixins/conditional_visibility.py +193 -0
- dynamicforms/mixins/enable_copy.py +4 -0
- dynamicforms/mixins/field_render.py +209 -0
- dynamicforms/mixins/file_field.py +5 -0
- dynamicforms/mixins/help_text.py +46 -0
- dynamicforms/mixins/implicit_hidden.py +7 -0
- dynamicforms/mixins/implicit_natural_date.py +88 -0
- dynamicforms/mixins/null_value.py +15 -0
- dynamicforms/mixins/password_field.py +23 -0
- dynamicforms/mixins/related_field_ajax.py +142 -0
- dynamicforms/mixins/rtf_field.py +58 -0
- dynamicforms/models.py +5 -0
- dynamicforms/models_utils.py +33 -0
- dynamicforms/pagination.py +175 -0
- dynamicforms/preupload_files.py +42 -0
- dynamicforms/progress.py +71 -0
- dynamicforms/renderers.py +1 -0
- dynamicforms/routers.py +36 -0
- dynamicforms/serializers.py +486 -0
- dynamicforms/struct.py +40 -0
- dynamicforms/template_render/__init__.py +10 -0
- dynamicforms/template_render/layout.py +178 -0
- dynamicforms/template_render/mixins/__init__.py +0 -0
- dynamicforms/template_render/mixins/base.py +39 -0
- dynamicforms/template_render/mixins/field.py +74 -0
- dynamicforms/template_render/mixins/listserializer.py +100 -0
- dynamicforms/template_render/mixins/render_mode_enum.py +8 -0
- dynamicforms/template_render/mixins/serializer.py +301 -0
- dynamicforms/template_render/mixins/serializer_filter.py +19 -0
- dynamicforms/template_render/mixins/serializer_render_actions.py +21 -0
- dynamicforms/template_render/mixins/serializer_render_fields.py +57 -0
- dynamicforms/template_render/mixins/util.py +9 -0
- dynamicforms/template_render/renderer.py +38 -0
- dynamicforms/template_render/responsive_table_layout.py +64 -0
- dynamicforms/urls.py +9 -0
- dynamicforms/utils.py +32 -0
- dynamicforms/views.py +1 -0
- dynamicforms/viewsets.py +421 -0
- dynamicforms-0.76.14.dist-info/METADATA +129 -0
- dynamicforms-0.76.14.dist-info/RECORD +287 -0
- dynamicforms-0.76.14.dist-info/WHEEL +4 -0
- dynamicforms-0.76.14.dist-info/licenses/LICENSE +21 -0
- dynamicforms_legacy/__init__.py +8 -0
- dynamicforms_legacy/action.py +561 -0
- dynamicforms_legacy/apps.py +5 -0
- dynamicforms_legacy/context_processors.py +27 -0
- dynamicforms_legacy/dynamicforms_migration_mixin.py +42 -0
- dynamicforms_legacy/fields.py +1108 -0
- dynamicforms_legacy/filters.py +44 -0
- dynamicforms_legacy/migrations/__init__.py +0 -0
- dynamicforms_legacy/mixins/__init__.py +16 -0
- dynamicforms_legacy/mixins/action.py +14 -0
- dynamicforms_legacy/mixins/allow_tags.py +85 -0
- dynamicforms_legacy/mixins/enable_copy.py +4 -0
- dynamicforms_legacy/mixins/file_field.py +5 -0
- dynamicforms_legacy/mixins/help_text.py +38 -0
- dynamicforms_legacy/mixins/implicit_hidden.py +7 -0
- dynamicforms_legacy/mixins/implicit_natural_date.py +86 -0
- dynamicforms_legacy/mixins/null_choice.py +26 -0
- dynamicforms_legacy/mixins/null_value.py +15 -0
- dynamicforms_legacy/mixins/password_field.py +19 -0
- dynamicforms_legacy/mixins/related_field_ajax.py +58 -0
- dynamicforms_legacy/mixins/render.py +203 -0
- dynamicforms_legacy/mixins/rtf_field.py +52 -0
- dynamicforms_legacy/mixins/single_choice.py +21 -0
- dynamicforms_legacy/models.py +5 -0
- dynamicforms_legacy/pagination.py +168 -0
- dynamicforms_legacy/progress.py +71 -0
- dynamicforms_legacy/renderers.py +183 -0
- dynamicforms_legacy/serializers.py +335 -0
- dynamicforms_legacy/settings.py +196 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/css/bootstrap-theme.css +587 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css +6 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/css/bootstrap.css +6834 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/css/bootstrap.min.css +6 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.eot +0 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.svg +288 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.ttf +0 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.woff +0 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.woff2 +0 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/js/bootstrap.js +2580 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/js/bootstrap.min.js +6 -0
- dynamicforms_legacy/static/bootstrap-3.4.1-dist/js/npm.js +13 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/css/bootstrap-grid.css +3719 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/css/bootstrap-grid.css.map +1 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/css/bootstrap-grid.min.css +7 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/css/bootstrap-grid.min.css.map +1 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/css/bootstrap-reboot.css +331 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/css/bootstrap-reboot.css.map +1 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/css/bootstrap-reboot.min.css +8 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/css/bootstrap-reboot.min.css.map +1 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/css/bootstrap.css +10038 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/css/bootstrap.css.map +1 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/css/bootstrap.min.css +7 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/css/bootstrap.min.css.map +1 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/js/bootstrap.bundle.js +7013 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/js/bootstrap.bundle.js.map +1 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/js/bootstrap.bundle.min.js +7 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/js/bootstrap.bundle.min.js.map +1 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/js/bootstrap.js +4435 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/js/bootstrap.js.map +1 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/js/bootstrap.min.js +7 -0
- dynamicforms_legacy/static/bootstrap-4.3.1-dist/js/bootstrap.min.js.map +1 -0
- dynamicforms_legacy/static/ckeditor-df/ckEditorCustomBuildReadMe.txt +255 -0
- dynamicforms_legacy/static/ckeditor-df/ckeditor.js +6 -0
- dynamicforms_legacy/static/dynamicforms/dynamicforms.js +1883 -0
- dynamicforms_legacy/static/dynamicforms/icons/copy.svg +7 -0
- dynamicforms_legacy/static/dynamicforms/icons/eye-off-outline.svg +10 -0
- dynamicforms_legacy/static/dynamicforms/icons/eye-outline.svg +7 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-icons_222222_256x240.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-icons_228ef1_256x240.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-icons_ef8c08_256x240.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-icons_ffd27a_256x240.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/images/ui-icons_ffffff_256x240.png +0 -0
- dynamicforms_legacy/static/jquery_ui/css/jquery-ui.min.css +7 -0
- dynamicforms_legacy/static/jquery_ui/css/jquery-ui.structure.min.css +5 -0
- dynamicforms_legacy/static/jquery_ui/css/jquery-ui.theme.min.css +5 -0
- dynamicforms_legacy/static/jquery_ui/js/jquery-3.3.1.min.js +2 -0
- dynamicforms_legacy/static/jquery_ui/js/jquery-ui.min.js +13 -0
- dynamicforms_legacy/static/select2/css/select2-bootstrap3.min.css +7 -0
- dynamicforms_legacy/static/select2/css/select2-bootstrap4.min.css +578 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/css/select2.min.css +1 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/af.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/ar.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/az.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/bg.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/bs.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/ca.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/cs.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/da.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/de.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/dsb.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/el.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/en.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/es.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/et.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/eu.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/fa.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/fi.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/fr.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/gl.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/he.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/hi.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/hr.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/hsb.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/hu.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/hy.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/id.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/is.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/it.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/ja.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/km.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/ko.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/lt.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/lv.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/mk.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/ms.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/nb.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/nl.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/pl.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/ps.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/pt-BR.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/pt.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/ro.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/ru.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/sk.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/sl.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/sr-Cyrl.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/sr.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/sv.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/th.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/tr.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/uk.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/vi.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/zh-CN.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/i18n/zh-TW.js +3 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/select2.full.js +6457 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/select2.full.min.js +1 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/select2.js +5746 -0
- dynamicforms_legacy/static/select2/select2-4.0.5/js/select2.min.js +1 -0
- dynamicforms_legacy/struct.py +40 -0
- dynamicforms_legacy/templates/dynamicforms/base.html +12 -0
- dynamicforms_legacy/templates/dynamicforms/base_form.html +44 -0
- dynamicforms_legacy/templates/dynamicforms/base_includes.html +51 -0
- dynamicforms_legacy/templates/dynamicforms/base_includes_select2.html +6 -0
- dynamicforms_legacy/templates/dynamicforms/base_list.html +47 -0
- dynamicforms_legacy/templates/dynamicforms/base_table_body.html +46 -0
- dynamicforms_legacy/templates/dynamicforms/base_table_filter.html +44 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/base_form.html +10 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/base_includes.html +46 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/base_includes_v3.html +90 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/base_includes_v4.html +77 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/base_list.html +9 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/base_table_body.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/base_table_filter.html +5 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/base_field.html +41 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/base_field_v3.html +11 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/base_field_v4.html +11 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/checkbox.html +7 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/checkbox_multiple.html +7 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/fieldset.html +21 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/form.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/input.html +31 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/list_fieldset.html +18 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/radio.html +8 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/rtf_field.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/select.html +46 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/select_multiple.html +17 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/field/textarea.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/modal_dialog.html +13 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/modal_dialog_rest.html +23 -0
- dynamicforms_legacy/templates/dynamicforms/bootstrap/page.html +11 -0
- dynamicforms_legacy/templates/dynamicforms/field/base_field.html +55 -0
- dynamicforms_legacy/templates/dynamicforms/field/checkbox.html +28 -0
- dynamicforms_legacy/templates/dynamicforms/field/checkbox_multiple.html +16 -0
- dynamicforms_legacy/templates/dynamicforms/field/dict_field.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/field/fieldset.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/field/form.html +18 -0
- dynamicforms_legacy/templates/dynamicforms/field/input.html +31 -0
- dynamicforms_legacy/templates/dynamicforms/field/list_field.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/field/list_fieldset.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/field/radio.html +24 -0
- dynamicforms_legacy/templates/dynamicforms/field/rtf_field.html +14 -0
- dynamicforms_legacy/templates/dynamicforms/field/select.html +97 -0
- dynamicforms_legacy/templates/dynamicforms/field/select_multiple.html +67 -0
- dynamicforms_legacy/templates/dynamicforms/field/textarea.html +13 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/base_form.html +13 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/base_includes.html +225 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/base_list.html +9 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/base_table_body.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/base_table_filter.html +5 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/field/base_field.html +16 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/field/checkbox.html +7 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/field/checkbox_multiple.html +9 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/field/fieldset.html +11 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/field/form.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/field/input.html +17 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/field/list_fieldset.html +6 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/field/radio.html +8 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/field/rtf_field.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/field/select.html +37 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/field/select_multiple.html +18 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/field/textarea.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/modal_dialog.html +2 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/modal_dialog_rest.html +27 -0
- dynamicforms_legacy/templates/dynamicforms/jquery_ui/page.html +20 -0
- dynamicforms_legacy/templates/dynamicforms/modal_dialog.html +4 -0
- dynamicforms_legacy/templates/dynamicforms/modal_dialog_rest.html +18 -0
- dynamicforms_legacy/templates/dynamicforms/page.html +25 -0
- dynamicforms_legacy/templates/dynamicforms/rtf_field_style.html +41 -0
- dynamicforms_legacy/templatetags/__init__.py +0 -0
- dynamicforms_legacy/templatetags/dynamicforms.py +475 -0
- dynamicforms_legacy/urls.py +8 -0
- dynamicforms_legacy/views.py +1 -0
- dynamicforms_legacy/viewsets.py +366 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import django_filters.rest_framework as filters
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ListWithFields(list):
|
|
5
|
+
def __init__(self, fields, lst) -> None:
|
|
6
|
+
super().__init__()
|
|
7
|
+
self.fields = fields
|
|
8
|
+
self.extend(lst)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FilterBackend(filters.DjangoFilterBackend):
|
|
12
|
+
"""
|
|
13
|
+
Our own FilterBackend that also tries to read order from the pagination class's ordering field
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def get_filterset_class(self, view, queryset=None):
|
|
17
|
+
# here we look for any pagination class and its ordering attribute. if there is none, we sort by id
|
|
18
|
+
__paginator_ordering = getattr(getattr(view, "pagination_class", 0), "ordering", ["id"])
|
|
19
|
+
if isinstance(__paginator_ordering, str):
|
|
20
|
+
__paginator_ordering = [__paginator_ordering]
|
|
21
|
+
|
|
22
|
+
ordering_excluded_fields = getattr(view, "filterset_ordering_exclude", [])
|
|
23
|
+
ordering_default = getattr(view, "filterset_ordering_default", __paginator_ordering)
|
|
24
|
+
|
|
25
|
+
class MyOrderingFilter(filters.OrderingFilter):
|
|
26
|
+
def get_ordering(self, value):
|
|
27
|
+
if not value:
|
|
28
|
+
return ListWithFields(list(self.param_map.keys()), ordering_default)
|
|
29
|
+
return ListWithFields(list(self.param_map.keys()), [self.get_ordering_value(param) for param in value])
|
|
30
|
+
|
|
31
|
+
class FilterSetApplied(filters.FilterSet):
|
|
32
|
+
ordering = MyOrderingFilter(
|
|
33
|
+
fields=[f.name for f in queryset.model._meta.fields if f.name not in ordering_excluded_fields]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return FilterSetApplied
|
|
37
|
+
|
|
38
|
+
def get_ordering(self, request, queryset, view):
|
|
39
|
+
filterset = self.get_filterset(request, queryset, view)
|
|
40
|
+
filterset.is_valid()
|
|
41
|
+
ordering_filters = {k: v for k, v in filterset.filters.items() if isinstance(v, filters.OrderingFilter)}
|
|
42
|
+
for name, value in filterset.form.cleaned_data.items():
|
|
43
|
+
if name in ordering_filters:
|
|
44
|
+
return ordering_filters[name].get_ordering(value)
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# flake8: noqa
|
|
2
|
+
|
|
3
|
+
from .action import ActionMixin
|
|
4
|
+
from .allow_tags import AllowTagsMixin
|
|
5
|
+
from .enable_copy import EnableCopyMixin
|
|
6
|
+
from .file_field import FileFieldMixin
|
|
7
|
+
from .help_text import FieldHelpTextMixin
|
|
8
|
+
from .implicit_hidden import HiddenFieldMixin
|
|
9
|
+
from .implicit_natural_date import DateFieldMixin, DateTimeFieldMixin, TimeFieldMixin
|
|
10
|
+
from .null_choice import NullChoiceMixin
|
|
11
|
+
from .null_value import NullValueMixin
|
|
12
|
+
from .password_field import PasswordFieldMixin
|
|
13
|
+
from .related_field_ajax import RelatedFieldAJAXMixin
|
|
14
|
+
from .render import DisplayMode, RenderMixin
|
|
15
|
+
from .rtf_field import RTFFieldMixin
|
|
16
|
+
from .single_choice import SingleChoiceMixin
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from dynamicforms_legacy.action import Actions
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ActionMixin(object):
|
|
5
|
+
"""
|
|
6
|
+
Used in fields allowing declaration of actions that happen when field values change
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, *args, actions: Actions = None, **kwargs):
|
|
10
|
+
super().__init__(*args, **kwargs)
|
|
11
|
+
act = actions or Actions(None)
|
|
12
|
+
act.actions.extend(getattr(self, "actions", Actions()).actions)
|
|
13
|
+
# Obtain a personalised list of actions
|
|
14
|
+
self.actions = act.get_resolved_copy(self)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# TODO: Implementation of this mixin is wrong for ChoiceField (single choice variant):
|
|
2
|
+
# The ChoiceField will return data in an array even for (Single)ChoiceField. Select2 handles this, maybe even requires
|
|
3
|
+
# this, but some other component might not
|
|
4
|
+
import ast
|
|
5
|
+
|
|
6
|
+
from rest_framework.fields import ChoiceField, MultipleChoiceField
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DenormalisedArray(list):
|
|
10
|
+
def __init__(self, lst, field):
|
|
11
|
+
self.field = field
|
|
12
|
+
if lst is not None and isinstance(lst, str) and isinstance(field, MultipleChoiceField):
|
|
13
|
+
for itm in ast.literal_eval(lst):
|
|
14
|
+
self.append(itm)
|
|
15
|
+
|
|
16
|
+
elif lst is not None and isinstance(lst, list):
|
|
17
|
+
for itm in lst:
|
|
18
|
+
self.append(itm)
|
|
19
|
+
|
|
20
|
+
elif lst is not None:
|
|
21
|
+
self.append(lst)
|
|
22
|
+
|
|
23
|
+
def __str__(self):
|
|
24
|
+
choices = self.field.choices
|
|
25
|
+
ret = ""
|
|
26
|
+
count = 0
|
|
27
|
+
for item in self:
|
|
28
|
+
ret += ", " if count else ""
|
|
29
|
+
if item in choices:
|
|
30
|
+
ret += choices[item]
|
|
31
|
+
else:
|
|
32
|
+
ret += item
|
|
33
|
+
count += 1
|
|
34
|
+
return ret
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AllowTagsMixin(object):
|
|
38
|
+
def __init__(self, *args, allow_tags=False, **kwargs):
|
|
39
|
+
super().__init__(*args, **kwargs)
|
|
40
|
+
self.allow_tags = allow_tags or False
|
|
41
|
+
|
|
42
|
+
def to_internal_value(self, data):
|
|
43
|
+
"""
|
|
44
|
+
Validator is taken directly from DRF and updated, to check for allow_tags.
|
|
45
|
+
Option allow_tags enables adding value that is not on the options list.
|
|
46
|
+
"""
|
|
47
|
+
if not self.allow_tags:
|
|
48
|
+
data = super().to_internal_value(data)
|
|
49
|
+
|
|
50
|
+
if isinstance(self, MultipleChoiceField):
|
|
51
|
+
if isinstance(data, str) or not hasattr(data, "__iter__"):
|
|
52
|
+
self.fail("not_a_list", input_type=type(data).__name__)
|
|
53
|
+
if not self.allow_empty and len(data) == 0:
|
|
54
|
+
self.fail("empty")
|
|
55
|
+
|
|
56
|
+
return data
|
|
57
|
+
|
|
58
|
+
elif isinstance(self, ChoiceField) and not isinstance(self, MultipleChoiceField):
|
|
59
|
+
if data == "" and self.allow_blank:
|
|
60
|
+
return ""
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
if not self.allow_tags:
|
|
64
|
+
return self.choice_strings_to_values[str(data)]
|
|
65
|
+
|
|
66
|
+
return data
|
|
67
|
+
except KeyError:
|
|
68
|
+
self.fail("invalid_choice", input=data)
|
|
69
|
+
|
|
70
|
+
def to_representation(self, value, row_data=None):
|
|
71
|
+
if isinstance(self, ChoiceField) and self.allow_tags and value:
|
|
72
|
+
return DenormalisedArray(value, self)
|
|
73
|
+
|
|
74
|
+
return super().to_representation(value, row_data)
|
|
75
|
+
|
|
76
|
+
def iter_options_bound(self, value):
|
|
77
|
+
# noinspection PyUnresolvedReferences
|
|
78
|
+
if isinstance(value, list):
|
|
79
|
+
for item in value:
|
|
80
|
+
if item not in self.choices.keys():
|
|
81
|
+
self.grouped_choices[item] = item
|
|
82
|
+
elif isinstance(value, str) and self.allow_tags and value not in self.choices.keys():
|
|
83
|
+
self.grouped_choices[value] = value
|
|
84
|
+
|
|
85
|
+
return super().iter_options_bound(value)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Optional, Union
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FieldHelpTextMixin(object):
|
|
5
|
+
"""
|
|
6
|
+
Redefines help_text property such that we can set help text for api docs and form help text
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self, *args, help_text: Union[str, dict, None] = None, help_text_form: Optional[str] = None, **kwargs
|
|
11
|
+
) -> None:
|
|
12
|
+
super().__init__(*args, help_text=help_text, **kwargs)
|
|
13
|
+
self.help_text = help_text
|
|
14
|
+
if help_text_form or not isinstance(help_text, dict):
|
|
15
|
+
self._help_text_form = help_text_form or self.help_text
|
|
16
|
+
|
|
17
|
+
def get_help_text(self):
|
|
18
|
+
return self._help_text
|
|
19
|
+
|
|
20
|
+
def set_help_text(self, value):
|
|
21
|
+
if isinstance(value, dict):
|
|
22
|
+
self._help_text = value.get("doc", None)
|
|
23
|
+
self._help_text_form = value.get("form", None)
|
|
24
|
+
else:
|
|
25
|
+
self._help_text = value
|
|
26
|
+
self._help_text_form = getattr(self, "_help_text_form", None) or value
|
|
27
|
+
|
|
28
|
+
def get_help_text_form(self):
|
|
29
|
+
return self._help_text_form
|
|
30
|
+
|
|
31
|
+
def set_help_text_form(self, value):
|
|
32
|
+
if isinstance(value, dict):
|
|
33
|
+
self._help_text = value.get("form", None)
|
|
34
|
+
else:
|
|
35
|
+
self._help_text_form = value
|
|
36
|
+
|
|
37
|
+
help_text = property(get_help_text, set_help_text)
|
|
38
|
+
help_text_form = property(get_help_text_form, set_help_text_form)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from django.utils import timezone
|
|
6
|
+
from django.utils.formats import localize
|
|
7
|
+
from rest_framework.fields import DateField, TimeField
|
|
8
|
+
|
|
9
|
+
from .render import RenderMixin
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NaturalDateTimeMixin(object):
|
|
13
|
+
"""
|
|
14
|
+
Used for rendering datetime in human natural style (e.g.: 1 hour, 10 minutes ago)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, *args, table_format: str = "", **kwargs) -> None:
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
:param args:
|
|
21
|
+
:param table_format: Format for datetime rendering in table (non editable). If format is %N:{precision},
|
|
22
|
+
datetime will render in natural style with {precision} depth
|
|
23
|
+
:param kwargs:
|
|
24
|
+
"""
|
|
25
|
+
super().__init__(*args, **kwargs)
|
|
26
|
+
self.table_format = table_format
|
|
27
|
+
|
|
28
|
+
# noinspection PyUnresolvedReferences
|
|
29
|
+
def render_to_table(self, value, row_data):
|
|
30
|
+
if value is not None:
|
|
31
|
+
output_format = getattr(self, "table_format", None)
|
|
32
|
+
if output_format is not None:
|
|
33
|
+
if re.match(r"%N:\d+", output_format):
|
|
34
|
+
imported = False
|
|
35
|
+
try:
|
|
36
|
+
# This library (will-natural) is used because is the only one we could find that adds text
|
|
37
|
+
# saying when is this datetime ("ago" or "from now") and have the possibility to set max
|
|
38
|
+
# precision
|
|
39
|
+
|
|
40
|
+
# noinspection PyPackageRequirements
|
|
41
|
+
from natural.date import duration
|
|
42
|
+
|
|
43
|
+
imported = True
|
|
44
|
+
except:
|
|
45
|
+
print("Install library for natural presentation of date (pip install will-natural)")
|
|
46
|
+
|
|
47
|
+
if imported:
|
|
48
|
+
if isinstance(self, DateField):
|
|
49
|
+
now = timezone.now().date()
|
|
50
|
+
elif isinstance(self, TimeField):
|
|
51
|
+
now = datetime.now()
|
|
52
|
+
value = datetime.now().replace(
|
|
53
|
+
hour=value.hour, minute=value.minute, second=value.second, microsecond=value.microsecond
|
|
54
|
+
)
|
|
55
|
+
else:
|
|
56
|
+
now = timezone.now()
|
|
57
|
+
|
|
58
|
+
# noinspection PyUnboundLocalVariable
|
|
59
|
+
return duration(value, now=now, precision=int(output_format.split(":")[1]))
|
|
60
|
+
else:
|
|
61
|
+
# Invoke DRF field's to_representation
|
|
62
|
+
global_format = getattr(self, "format", None)
|
|
63
|
+
setattr(self, "format", output_format)
|
|
64
|
+
# noinspection PySuperArguments
|
|
65
|
+
value = super(RenderMixin, self).to_representation(value) # Skip RenderMixin
|
|
66
|
+
setattr(self, "format", global_format)
|
|
67
|
+
return value or ""
|
|
68
|
+
|
|
69
|
+
return localize(value)
|
|
70
|
+
|
|
71
|
+
return super().render_to_table(value, row_data)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class TimeFieldMixin(NaturalDateTimeMixin):
|
|
75
|
+
def __init__(self, *args, table_format: str = None, **kwargs) -> None:
|
|
76
|
+
super().__init__(*args, table_format=table_format, **kwargs)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class DateFieldMixin(NaturalDateTimeMixin):
|
|
80
|
+
def __init__(self, *args, table_format: str = None, **kwargs) -> None:
|
|
81
|
+
super().__init__(*args, table_format=table_format, **kwargs)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class DateTimeFieldMixin(NaturalDateTimeMixin):
|
|
85
|
+
def __init__(self, *args, table_format: str = None, **kwargs) -> None:
|
|
86
|
+
super().__init__(*args, table_format=table_format, **kwargs)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from rest_framework.fields import ChoiceField
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NullChoiceMixin(object):
|
|
5
|
+
"""
|
|
6
|
+
Declaring ChoiceField's null_choice_text
|
|
7
|
+
(what should be rendered for "no selection"; DRF's default is ---------)
|
|
8
|
+
We also support user declaring a choice with None value and a different text (see hidden fields example)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def null_choice_text(self):
|
|
13
|
+
res = "--------"
|
|
14
|
+
if isinstance(self, ChoiceField) and not getattr(self, "get_queryset", None):
|
|
15
|
+
# Only do this for true ChoiceFields. RelatedFields would run a query here
|
|
16
|
+
# Possibly we will at some point have to enable this for relatedfields too
|
|
17
|
+
for opt in self.iter_options():
|
|
18
|
+
if opt.start_option_group or opt.end_option_group:
|
|
19
|
+
pass
|
|
20
|
+
elif opt.value is None:
|
|
21
|
+
res = opt.display_text
|
|
22
|
+
return res
|
|
23
|
+
|
|
24
|
+
def iter_options_bound(self, value):
|
|
25
|
+
# noinspection PyUnresolvedReferences
|
|
26
|
+
return super().iter_options()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from rest_framework.fields import IntegerField
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NullValueMixin(object):
|
|
5
|
+
def to_internal_value(self, data):
|
|
6
|
+
if data == "" and self.allow_null:
|
|
7
|
+
self.validators = []
|
|
8
|
+
return None
|
|
9
|
+
return super().to_internal_value(data)
|
|
10
|
+
|
|
11
|
+
def to_representation(self, value, row_data=None):
|
|
12
|
+
if value is None and self.allow_null and not isinstance(self, IntegerField):
|
|
13
|
+
value = ""
|
|
14
|
+
|
|
15
|
+
return super().to_representation(value, row_data)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class PasswordFieldMixin(object):
|
|
2
|
+
def __init__(self, *args, password_field=False, **kwargs):
|
|
3
|
+
super().__init__(*args, **kwargs)
|
|
4
|
+
self.password_field = password_field
|
|
5
|
+
|
|
6
|
+
def set_password_field(self, value):
|
|
7
|
+
self._password_field = value
|
|
8
|
+
if value:
|
|
9
|
+
self.display_table = "DisplayMode.SUPRESS"
|
|
10
|
+
self.style.update(input_type="password")
|
|
11
|
+
else:
|
|
12
|
+
try:
|
|
13
|
+
self.style["input_type"]
|
|
14
|
+
self.style.update(input_type="text")
|
|
15
|
+
self.display_table = "DisplayMode.FULL"
|
|
16
|
+
except:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
password_field = property(lambda self: self._password_field, set_password_field)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RelatedFieldAJAXMixin(object):
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
*args,
|
|
8
|
+
url_reverse: Optional[str] = None,
|
|
9
|
+
placeholder: Optional[str] = None,
|
|
10
|
+
additional_parameters: Optional[dict] = None,
|
|
11
|
+
query_field: str = "query",
|
|
12
|
+
value_field: str = "choice_id",
|
|
13
|
+
text_field: str = "choice_text",
|
|
14
|
+
**kwargs
|
|
15
|
+
):
|
|
16
|
+
"""
|
|
17
|
+
Allows us to use AJAX to populate select2 options instead of pre-populating at render time
|
|
18
|
+
|
|
19
|
+
:param args:
|
|
20
|
+
:param url_reverse: reverse url to ViewSet providing the JSON data
|
|
21
|
+
:param placeholder: select2 placeholder to display until user selects a value
|
|
22
|
+
:param additional_parameters: additional parameters to be sent to ViewSet as part of the query
|
|
23
|
+
:param query_field: field against which user search will be performed
|
|
24
|
+
:param kwargs:
|
|
25
|
+
"""
|
|
26
|
+
super().__init__(*args, **kwargs)
|
|
27
|
+
self.url_reverse = url_reverse
|
|
28
|
+
self.placeholder = placeholder
|
|
29
|
+
self.additional_parameters = additional_parameters
|
|
30
|
+
self.query_field = query_field
|
|
31
|
+
self.value_field = value_field
|
|
32
|
+
self.text_field = text_field
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def additional_parameters_urlencoded(self):
|
|
36
|
+
from django.utils.http import urlencode
|
|
37
|
+
|
|
38
|
+
return "?" + urlencode(self.additional_parameters)
|
|
39
|
+
|
|
40
|
+
# noinspection PyUnresolvedReferences
|
|
41
|
+
def iter_options_bound(self, value):
|
|
42
|
+
if self.url_reverse:
|
|
43
|
+
if value is None:
|
|
44
|
+
return [dict(value="", display_text=self.placeholder)]
|
|
45
|
+
try:
|
|
46
|
+
if hasattr(self, "child_relation"):
|
|
47
|
+
itm = self.child_relation
|
|
48
|
+
qry = itm.get_queryset()
|
|
49
|
+
qry = qry.filter(pk__in=value)
|
|
50
|
+
else:
|
|
51
|
+
itm = self
|
|
52
|
+
qry = self.get_queryset()
|
|
53
|
+
qry = qry.filter(pk=value)
|
|
54
|
+
|
|
55
|
+
return [dict(value=rec.id, display_text=itm.display_value(rec)) for rec in qry.all()]
|
|
56
|
+
except:
|
|
57
|
+
return []
|
|
58
|
+
return super().iter_options()
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import uuid as uuid_module
|
|
2
|
+
|
|
3
|
+
from enum import IntEnum
|
|
4
|
+
from typing import Hashable
|
|
5
|
+
|
|
6
|
+
from rest_framework.fields import Field as DrfField
|
|
7
|
+
from rest_framework.relations import ManyRelatedField, PKOnlyObject, RelatedField
|
|
8
|
+
from rest_framework.serializers import ListSerializer
|
|
9
|
+
from rest_framework.templatetags import rest_framework as drftt
|
|
10
|
+
|
|
11
|
+
from dynamicforms_legacy.settings import DYNAMICFORMS
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DisplayMode(IntEnum):
|
|
15
|
+
SUPPRESS = 1 # Field will be entirely suppressed. it will not render (not even to JSON) and will not parse for PUT
|
|
16
|
+
HIDDEN = 5 # Field will render as <input type="hidden"> or <tr data-field_name>
|
|
17
|
+
INVISIBLE = 8 # Field will render completely, but with display: none. Equal to setting its style = {display: none}
|
|
18
|
+
FULL = 10 # Field will render completely
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RenderMixin(object):
|
|
22
|
+
"""
|
|
23
|
+
Is used in fields and serializers, so every field and serializer gets its unique id. Also to specify where and how
|
|
24
|
+
fields should render.
|
|
25
|
+
|
|
26
|
+
In form where serializer is used, id is serializers uuid. Table with list of records has id »list-serializer.uuid«,
|
|
27
|
+
in dialog id is »dialog-{serializer.uuid}« and save button's id on dialog is »save-{serializer.uuid}«
|
|
28
|
+
|
|
29
|
+
Similar for fields: All inputs in HTML get id from field.uuid. Div that contains all that belongs to the field has
|
|
30
|
+
»container-{field.uuid}« for id, label has »label-{field.uuid}« and help text (if exists) has »help-{field.uuid}«
|
|
31
|
+
for id.
|
|
32
|
+
|
|
33
|
+
Used for rendering individual field to table view
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
*args,
|
|
39
|
+
uuid: uuid_module.UUID = None,
|
|
40
|
+
display: DisplayMode = None, # None == Leave at default
|
|
41
|
+
display_table: DisplayMode = None, # None == Leave at default
|
|
42
|
+
display_form: DisplayMode = None, # None == Leave at default
|
|
43
|
+
table_classes: str = "",
|
|
44
|
+
**kwargs
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
:param args: passed on to inherited constructors
|
|
49
|
+
:param uuid: custom specified field UUID. if not specified, it will be assigned automatically
|
|
50
|
+
:param display: see DisplayMode enum. Specifies how field will render. Leave at None for default (FULL)
|
|
51
|
+
display_form and display_table also accepted for better granularity
|
|
52
|
+
:param table_classes: css classes to add to the table column
|
|
53
|
+
:param kwargs: passed on to inherited constructors
|
|
54
|
+
"""
|
|
55
|
+
super().__init__(*args, **kwargs)
|
|
56
|
+
self.uuid = uuid or uuid_module.uuid1()
|
|
57
|
+
# noinspection PyUnresolvedReferences
|
|
58
|
+
self.display_table = (
|
|
59
|
+
display_table
|
|
60
|
+
or display
|
|
61
|
+
or (DisplayMode.FULL if not getattr(self, "write_only", False) else DisplayMode.SUPPRESS)
|
|
62
|
+
)
|
|
63
|
+
self.display_form = display_form or display or DisplayMode.FULL
|
|
64
|
+
self.table_classes = table_classes
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def is_rendering_to_list(self):
|
|
68
|
+
"""
|
|
69
|
+
reports whether we are currently rendering to table or to single record
|
|
70
|
+
:return:
|
|
71
|
+
"""
|
|
72
|
+
try:
|
|
73
|
+
# noinspection PyUnresolvedReferences
|
|
74
|
+
base = self.parent
|
|
75
|
+
while base:
|
|
76
|
+
if isinstance(base, ListSerializer):
|
|
77
|
+
# If fields parent's parent is the ListSerializer, we're rendering to list
|
|
78
|
+
return True
|
|
79
|
+
base = base.parent
|
|
80
|
+
except:
|
|
81
|
+
pass
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def is_rendering_to_html(self):
|
|
86
|
+
try:
|
|
87
|
+
# noinspection PyUnresolvedReferences
|
|
88
|
+
return self.context["format"] == "html"
|
|
89
|
+
except:
|
|
90
|
+
pass
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
# noinspection PyUnresolvedReferences
|
|
94
|
+
def use_pk_only_optimization(self):
|
|
95
|
+
"""
|
|
96
|
+
Overrides DRF RelatedField's method. It True is returned then value passed for serialisation will be PK value
|
|
97
|
+
only, not entire relation object. we don't want that because then we can't resolve the value into something more
|
|
98
|
+
human-readable
|
|
99
|
+
:return:
|
|
100
|
+
"""
|
|
101
|
+
if self.is_rendering_to_list and self.is_rendering_to_html:
|
|
102
|
+
return False
|
|
103
|
+
return super().use_pk_only_optimization()
|
|
104
|
+
|
|
105
|
+
# noinspection PyUnresolvedReferences
|
|
106
|
+
def to_representation(self, value, row_data=None):
|
|
107
|
+
"""
|
|
108
|
+
Overrides DRF Field's to_representation.
|
|
109
|
+
Note that this is also called for the entire record as well as the serializer also is a Field descendant
|
|
110
|
+
:param value: value to serialize
|
|
111
|
+
:param row_data: instance with row data
|
|
112
|
+
:return: serialized value
|
|
113
|
+
"""
|
|
114
|
+
if self.is_rendering_to_list and self.is_rendering_to_html and self.display_table != DisplayMode.HIDDEN:
|
|
115
|
+
# if rentering to html table, let's try to resolve any lookups
|
|
116
|
+
# hidden fields will render to tr data-field_name attributes, so we maybe want to have ids, not text there
|
|
117
|
+
# we have discussed alternatives but decided that right now a more complete solution is not needed
|
|
118
|
+
return self.render_to_table(value, row_data)
|
|
119
|
+
|
|
120
|
+
check_for_none = value.pk if isinstance(value, PKOnlyObject) else value
|
|
121
|
+
if check_for_none is None:
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
return super().to_representation(value)
|
|
125
|
+
|
|
126
|
+
def set_display(self, value):
|
|
127
|
+
if isinstance(value, tuple):
|
|
128
|
+
self.display_form, self.display_table = value
|
|
129
|
+
else:
|
|
130
|
+
self.display_form = self.display_table = value
|
|
131
|
+
|
|
132
|
+
display = property(lambda self: self.display_form, set_display)
|
|
133
|
+
|
|
134
|
+
# noinspection PyUnusedLocal, PyUnresolvedReferences
|
|
135
|
+
def render_to_table(self, value, row_data):
|
|
136
|
+
"""
|
|
137
|
+
Renders field value for table view
|
|
138
|
+
|
|
139
|
+
:param value: field value
|
|
140
|
+
:param row_data: data for entire row (for more complex renderers)
|
|
141
|
+
:return: rendered value for table view
|
|
142
|
+
"""
|
|
143
|
+
get_queryset = getattr(self, "get_queryset", None)
|
|
144
|
+
|
|
145
|
+
if isinstance(self, ManyRelatedField):
|
|
146
|
+
# Hm, not sure if this is the final thing to do: an example of this field is in
|
|
147
|
+
# ALC plane editor (modes of takeoff). However, value is a queryset here. There seem to still be DB queries
|
|
148
|
+
# However, in the example I have, the problem is solved by doing prefetch_related on the m2m relation
|
|
149
|
+
cr = self.child_relation
|
|
150
|
+
return ", ".join((cr.display_value(item) for item in value))
|
|
151
|
+
# return ', '.join((cr.display_value(item) for item in cr.get_queryset().filter(pk__in=value)))
|
|
152
|
+
elif isinstance(self, RelatedField) or get_queryset:
|
|
153
|
+
return self.display_value(value)
|
|
154
|
+
else:
|
|
155
|
+
choices = getattr(self, "choices", {})
|
|
156
|
+
|
|
157
|
+
# Now that we got our choices for related & choice fields, let's first get the value as it would be by DRF
|
|
158
|
+
check_for_none = value.pk if isinstance(value, PKOnlyObject) else value
|
|
159
|
+
if check_for_none is None:
|
|
160
|
+
value = None
|
|
161
|
+
else:
|
|
162
|
+
value = super().to_representation(value)
|
|
163
|
+
|
|
164
|
+
if isinstance(value, Hashable) and value in choices:
|
|
165
|
+
# choice field: let's render display names, not values
|
|
166
|
+
value = choices[value]
|
|
167
|
+
if value is None:
|
|
168
|
+
return DYNAMICFORMS.null_text_table
|
|
169
|
+
|
|
170
|
+
return drftt.format_value(value)
|
|
171
|
+
|
|
172
|
+
def validate_empty_values(self: DrfField, data):
|
|
173
|
+
# noinspection PyUnresolvedReferences
|
|
174
|
+
res = super().validate_empty_values(data)
|
|
175
|
+
|
|
176
|
+
# This is to fix a problem with calculated fields which was only solved in DRF 3.10.
|
|
177
|
+
# Forces validation and inclusion of the field into validated data. See comment in original function.
|
|
178
|
+
if res == (True, None) and data is None and self.source == "*":
|
|
179
|
+
return False, None
|
|
180
|
+
return res
|
|
181
|
+
|
|
182
|
+
def ordering(self):
|
|
183
|
+
ordering = []
|
|
184
|
+
if hasattr(self, "context") and "view" in getattr(self, "context"):
|
|
185
|
+
ordering = getattr(self.context["view"], "ordering", None)
|
|
186
|
+
|
|
187
|
+
if getattr(self, "field_name") not in getattr(ordering, "fields", []):
|
|
188
|
+
return ""
|
|
189
|
+
|
|
190
|
+
index = -1
|
|
191
|
+
direction_asc = True
|
|
192
|
+
for idx, o in enumerate(ordering):
|
|
193
|
+
if o.startswith("-"):
|
|
194
|
+
direction_asc = False
|
|
195
|
+
o = o[1:]
|
|
196
|
+
if o == getattr(self, "field_name"):
|
|
197
|
+
index = idx
|
|
198
|
+
|
|
199
|
+
if index > -1:
|
|
200
|
+
direction_class = ("asc" if direction_asc else "desc") + " seg-%d" % (index + 1)
|
|
201
|
+
else:
|
|
202
|
+
direction_class = "unsorted"
|
|
203
|
+
return "ordering " + direction_class
|