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,142 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from django.db.models.manager import BaseManager
|
|
6
|
+
from django.urls import reverse
|
|
7
|
+
from django.utils.http import urlencode
|
|
8
|
+
from rest_framework.relations import RelatedField
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RelatedFieldAJAXMixin(object):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
*args,
|
|
15
|
+
url_reverse: Optional[str] = None,
|
|
16
|
+
placeholder: Optional[str] = None,
|
|
17
|
+
additional_parameters: Optional[dict] = None,
|
|
18
|
+
query_field: str = "query",
|
|
19
|
+
value_field: str = "choice_id",
|
|
20
|
+
text_field: str = "choice_text",
|
|
21
|
+
**kwargs,
|
|
22
|
+
):
|
|
23
|
+
"""
|
|
24
|
+
Allows us to use AJAX to populate select2 options instead of pre-populating at render time
|
|
25
|
+
|
|
26
|
+
:param args:
|
|
27
|
+
:param url_reverse: reverse url to ViewSet providing the JSON data
|
|
28
|
+
:param placeholder: select2 placeholder to display until user selects a value
|
|
29
|
+
:param additional_parameters: additional parameters to be sent to ViewSet as part of the query
|
|
30
|
+
:param query_field: field against which user search will be performed
|
|
31
|
+
:param kwargs:
|
|
32
|
+
"""
|
|
33
|
+
super().__init__(*args, **kwargs)
|
|
34
|
+
self.url_reverse = url_reverse
|
|
35
|
+
self.placeholder = placeholder
|
|
36
|
+
self.additional_parameters = additional_parameters
|
|
37
|
+
self.query_field = query_field
|
|
38
|
+
self.value_field = value_field
|
|
39
|
+
self.text_field = text_field
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def additional_parameters_urlencoded(self):
|
|
43
|
+
return urlencode(self.additional_parameters) if self.additional_parameters else None
|
|
44
|
+
|
|
45
|
+
# noinspection PyUnresolvedReferences
|
|
46
|
+
def iter_options_bound(self, value):
|
|
47
|
+
if self.url_reverse:
|
|
48
|
+
if value is None:
|
|
49
|
+
return [dict(value="", display_text=self.placeholder)]
|
|
50
|
+
try:
|
|
51
|
+
if hasattr(self, "child_relation"):
|
|
52
|
+
itm = self.child_relation
|
|
53
|
+
qry = itm.get_queryset()
|
|
54
|
+
if value != "__all__":
|
|
55
|
+
qry = qry.filter(pk__in=value)
|
|
56
|
+
else:
|
|
57
|
+
itm = self
|
|
58
|
+
qry = self.get_queryset()
|
|
59
|
+
if value != "__all__":
|
|
60
|
+
qry = qry.filter(pk=value)
|
|
61
|
+
|
|
62
|
+
return [dict(value=rec.id, display_text=itm.display_value(rec)) for rec in qry.all()]
|
|
63
|
+
except:
|
|
64
|
+
return []
|
|
65
|
+
return super().iter_options()
|
|
66
|
+
|
|
67
|
+
def as_component_def(self) -> dict:
|
|
68
|
+
try:
|
|
69
|
+
res = super().as_component_def() # noqa
|
|
70
|
+
except AttributeError:
|
|
71
|
+
res = dict()
|
|
72
|
+
if self.url_reverse:
|
|
73
|
+
ajax = dict(
|
|
74
|
+
ajax=dict(
|
|
75
|
+
url_reverse=reverse(self.url_reverse, kwargs=dict(format="json")),
|
|
76
|
+
placeholder=self.placeholder,
|
|
77
|
+
additional_parameters=self.additional_parameters_urlencoded,
|
|
78
|
+
query_field=self.query_field,
|
|
79
|
+
value_field=self.value_field,
|
|
80
|
+
text_field=self.text_field,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
res.update(ajax)
|
|
84
|
+
qs_count = self.get_queryset().count()
|
|
85
|
+
|
|
86
|
+
if qs_count and qs_count <= 10:
|
|
87
|
+
res.update(
|
|
88
|
+
choices=map(
|
|
89
|
+
lambda option: dict(id=option["value"], text=option["display_text"]),
|
|
90
|
+
self.iter_options_bound("__all__"),
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
res.update(
|
|
95
|
+
choices=list(map(
|
|
96
|
+
lambda option: dict(id=option.value, text=option.display_text), self.iter_options_bound(None)
|
|
97
|
+
)
|
|
98
|
+
))
|
|
99
|
+
if len(res["choices"]) > 100:
|
|
100
|
+
print(
|
|
101
|
+
f"WARNING: {self.parent.__class__.__name__}.{self.field_name} should be converted to AJAX.",
|
|
102
|
+
file=sys.stderr
|
|
103
|
+
)
|
|
104
|
+
return res
|
|
105
|
+
|
|
106
|
+
# noinspection PyUnusedLocal, PyUnresolvedReferences
|
|
107
|
+
def render_to_table(self, value, row_data):
|
|
108
|
+
"""
|
|
109
|
+
Renders field value for table view :if rendering to html table, let's try to resolve any lookups
|
|
110
|
+
hidden fields will render to tr data-field_name attributes, so we maybe want to have ids, not text there,
|
|
111
|
+
but that's up to front end to decide
|
|
112
|
+
|
|
113
|
+
:param value: field value
|
|
114
|
+
:param row_data: data for entire row (for more complex renderers)
|
|
115
|
+
:return: rendered value for table view
|
|
116
|
+
"""
|
|
117
|
+
from dynamicforms.fields import ManyRelatedField
|
|
118
|
+
|
|
119
|
+
if isinstance(self, ManyRelatedField):
|
|
120
|
+
# Hm, not sure if this is the final thing to do: an example of this field is in
|
|
121
|
+
# ALC plane editor (modes of takeoff). However, value is a queryset here. There seem to still be DB queries
|
|
122
|
+
# However, in the example I have, the problem is solved by doing prefetch_related on the m2m relation
|
|
123
|
+
cr = self.child_relation
|
|
124
|
+
if isinstance(value, BaseManager):
|
|
125
|
+
value = value.all()
|
|
126
|
+
if value is None:
|
|
127
|
+
return ""
|
|
128
|
+
return ", ".join((cr.display_value(item) for item in value))
|
|
129
|
+
# return ', '.join((cr.display_value(item) for item in cr.get_queryset().filter(pk__in=value)))
|
|
130
|
+
elif isinstance(self, RelatedField):
|
|
131
|
+
return self.display_value(value)
|
|
132
|
+
|
|
133
|
+
return value
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def many_init(cls, *args, **kwargs):
|
|
137
|
+
list_kwargs = {"child_relation": cls(*args, **kwargs)}
|
|
138
|
+
from dynamicforms.fields import ManyRelatedField
|
|
139
|
+
|
|
140
|
+
return ManyRelatedField(
|
|
141
|
+
display_form=kwargs.get("display_form"), display_table=kwargs.get("display_table"), **list_kwargs
|
|
142
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from html.parser import HTMLParser
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RTFFieldMixin(object):
|
|
5
|
+
def __init__(self, *args, show_as_plain_text=False, plain_text_lines=3, plain_text_line_length=40, **kwargs):
|
|
6
|
+
super().__init__(*args, **kwargs)
|
|
7
|
+
self.parse = show_as_plain_text or False
|
|
8
|
+
self.max_lines = plain_text_lines
|
|
9
|
+
self.max_line_length = plain_text_line_length
|
|
10
|
+
self.parser = RTFFieldHTMLParser(max_lines=self.max_lines, max_line_length=self.max_line_length)
|
|
11
|
+
|
|
12
|
+
def to_representation(self, instance, row_data=None):
|
|
13
|
+
# TODO: why is this here and not in init?
|
|
14
|
+
self.style.update(input_type="input")
|
|
15
|
+
if not self.parent.is_filter:
|
|
16
|
+
self.render_params["form_component_name"] = "DCKEditor"
|
|
17
|
+
|
|
18
|
+
return super().to_representation(instance, row_data)
|
|
19
|
+
|
|
20
|
+
def render_to_table(self, value, row_data):
|
|
21
|
+
if not self.parent.is_filter and self.parse:
|
|
22
|
+
self.parser.feed(value)
|
|
23
|
+
instance = self.parser.to_string()
|
|
24
|
+
self.parser.reset()
|
|
25
|
+
return instance
|
|
26
|
+
return value
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RTFFieldHTMLParser(HTMLParser):
|
|
30
|
+
def __init__(self, max_lines, max_line_length):
|
|
31
|
+
self.text = []
|
|
32
|
+
self.curr_line_count = 1
|
|
33
|
+
self.curr_line = ""
|
|
34
|
+
self.max_lines = max_lines
|
|
35
|
+
self.max_line_length = max_line_length
|
|
36
|
+
super(RTFFieldHTMLParser, self).__init__()
|
|
37
|
+
|
|
38
|
+
def handle_data(self, data):
|
|
39
|
+
text = data.strip()
|
|
40
|
+
if self.curr_line_count <= self.max_lines:
|
|
41
|
+
if len(text) > 0:
|
|
42
|
+
self.curr_line += " " + text
|
|
43
|
+
line = self.curr_line[0 : self.max_line_length - 3]
|
|
44
|
+
if len(self.curr_line) >= self.max_line_length - 3:
|
|
45
|
+
line += "..."
|
|
46
|
+
line += "<br>"
|
|
47
|
+
self.text.append(line)
|
|
48
|
+
self.curr_line = ""
|
|
49
|
+
self.curr_line_count += 1
|
|
50
|
+
|
|
51
|
+
def reset(self):
|
|
52
|
+
self.text = []
|
|
53
|
+
self.curr_line_count = 1
|
|
54
|
+
self.curr_line = ""
|
|
55
|
+
super(RTFFieldHTMLParser, self).reset()
|
|
56
|
+
|
|
57
|
+
def to_string(self):
|
|
58
|
+
return "".join(self.text).strip()
|
dynamicforms/models.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class IntChoiceEnum(IntEnum):
|
|
5
|
+
def __new__(cls, *args, **kwargs):
|
|
6
|
+
obj = int.__new__(cls, args[0])
|
|
7
|
+
obj._value_ = args[0]
|
|
8
|
+
return obj
|
|
9
|
+
|
|
10
|
+
# ignore the first param since it's already set by new
|
|
11
|
+
def __init__(self, _: str, description: str = None, icon: str = None, data: dict = None):
|
|
12
|
+
self._description_ = description
|
|
13
|
+
self._icon_ = icon
|
|
14
|
+
self._data_ = data
|
|
15
|
+
|
|
16
|
+
description = property(lambda self: self._description_)
|
|
17
|
+
icon = property(lambda self: self._icon_)
|
|
18
|
+
data = property(lambda self: self._data_)
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def get_choices_tuple(cls):
|
|
22
|
+
return tuple((item.value, item.description) for item in cls)
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def get_df_tuple(cls):
|
|
26
|
+
return tuple(
|
|
27
|
+
(item.value, item.description, item.icon) if item.icon is not None else (item.value, item.description)
|
|
28
|
+
for item in cls
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def has_value(cls, value):
|
|
33
|
+
return value in cls.__members__.values()
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import rest_framework.pagination as drf_p
|
|
5
|
+
|
|
6
|
+
from django.db.models import DurationField, F, Q
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CursorPagination(drf_p.CursorPagination):
|
|
10
|
+
ordering = "pk"
|
|
11
|
+
NONE_VALUE = "{`None`}"
|
|
12
|
+
|
|
13
|
+
# noinspection PyAttributeOutsideInit
|
|
14
|
+
def paginate_queryset(self, queryset, request, view=None):
|
|
15
|
+
self.page_size = self.get_page_size(request)
|
|
16
|
+
if not self.page_size:
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
self.base_url = request.build_absolute_uri()
|
|
20
|
+
self.ordering = self.get_ordering(request, queryset, view)
|
|
21
|
+
if "id" not in self.ordering and "-id" not in self.ordering:
|
|
22
|
+
ordering = list(self.ordering)
|
|
23
|
+
ordering.append("id")
|
|
24
|
+
self.ordering = tuple(ordering)
|
|
25
|
+
|
|
26
|
+
self.cursor = self.decode_cursor(request)
|
|
27
|
+
if self.cursor is None:
|
|
28
|
+
(offset, reverse, current_position) = (0, False, None)
|
|
29
|
+
else:
|
|
30
|
+
(offset, reverse, current_position) = self.cursor
|
|
31
|
+
try:
|
|
32
|
+
if current_position:
|
|
33
|
+
current_position = json.loads(current_position)
|
|
34
|
+
except TypeError:
|
|
35
|
+
current_position = None
|
|
36
|
+
|
|
37
|
+
# Cursor pagination always enforces an ordering.
|
|
38
|
+
ordering = drf_p._reverse_ordering(self.ordering) if reverse else self.ordering
|
|
39
|
+
# Currently we force nulls_first in ascending order and nulls_last in descending order
|
|
40
|
+
ordering = [
|
|
41
|
+
getattr(F(field.lstrip("-")), "desc" if field.startswith("-") else "asc")(
|
|
42
|
+
**{"nulls_last" if field.startswith("-") else "nulls_first": True}
|
|
43
|
+
)
|
|
44
|
+
for field in ordering
|
|
45
|
+
]
|
|
46
|
+
queryset = queryset.order_by(*ordering)
|
|
47
|
+
|
|
48
|
+
# If we have a cursor with a fixed position then filter by that.
|
|
49
|
+
if current_position is not None:
|
|
50
|
+
|
|
51
|
+
def filter_segment(segment_len):
|
|
52
|
+
kwargs = {}
|
|
53
|
+
args = []
|
|
54
|
+
for idx in range(segment_len):
|
|
55
|
+
order = self.ordering[idx]
|
|
56
|
+
is_reversed = order.startswith("-")
|
|
57
|
+
order_attr = order.lstrip("-")
|
|
58
|
+
attr_value = self.field_to_python(queryset, order_attr, current_position[order_attr])
|
|
59
|
+
|
|
60
|
+
# Test for: (cursor reversed) XOR (queryset reversed)
|
|
61
|
+
if idx < segment_len - 1:
|
|
62
|
+
# if this is the last field in this segment
|
|
63
|
+
if attr_value == self.NONE_VALUE:
|
|
64
|
+
kwargs[order_attr + "__isnull"] = True
|
|
65
|
+
else:
|
|
66
|
+
kwargs[order_attr] = attr_value
|
|
67
|
+
elif self.cursor.reverse != is_reversed:
|
|
68
|
+
if attr_value == self.NONE_VALUE:
|
|
69
|
+
# It is impossible to go lower than None... so we just omit this segment
|
|
70
|
+
return None
|
|
71
|
+
else:
|
|
72
|
+
args.append(Q(**{order_attr + "__lt": attr_value}) | Q(**{order_attr + "__isnull": True}))
|
|
73
|
+
else:
|
|
74
|
+
if attr_value == self.NONE_VALUE:
|
|
75
|
+
kwargs[order_attr + "__isnull"] = False
|
|
76
|
+
else:
|
|
77
|
+
kwargs[order_attr + "__gt"] = attr_value
|
|
78
|
+
|
|
79
|
+
return Q(**kwargs) & Q(*args)
|
|
80
|
+
|
|
81
|
+
fltr = Q()
|
|
82
|
+
for seg_len in range(len(self.ordering)):
|
|
83
|
+
new_segment = filter_segment(seg_len + 1)
|
|
84
|
+
if new_segment:
|
|
85
|
+
fltr = fltr | new_segment
|
|
86
|
+
queryset = queryset.filter(fltr)
|
|
87
|
+
|
|
88
|
+
# If we have an offset cursor then offset the entire page by that amount.
|
|
89
|
+
# We also always fetch an extra item in order to determine if there is a
|
|
90
|
+
# page following on from this one.
|
|
91
|
+
results = list(queryset[offset : offset + self.page_size + 1])
|
|
92
|
+
self.page = list(results[: self.page_size])
|
|
93
|
+
if not self.page:
|
|
94
|
+
# In rest_framework/pagination.py/get_previous_link is expected that self.page is not empty
|
|
95
|
+
# if cursor offset != 0
|
|
96
|
+
self.cursor = drf_p.Cursor(offset=0, reverse=False, position=None)
|
|
97
|
+
|
|
98
|
+
# DF: contrary to DRF's implementation we always provide the "prev" and "next" links as there may appear
|
|
99
|
+
# new records before / after the ones we already read. Mechanism for detecting whether they actually appeared
|
|
100
|
+
# is another matter altogether :)
|
|
101
|
+
if current_position is None and results:
|
|
102
|
+
self.cursor = drf_p.Cursor(offset=0, reverse=False, position=None)
|
|
103
|
+
current_position = self._get_position_from_instance(results[0], self.ordering)
|
|
104
|
+
elif isinstance(current_position, dict):
|
|
105
|
+
current_position = json.dumps(current_position)
|
|
106
|
+
|
|
107
|
+
# Determine the position of the final item following the page.
|
|
108
|
+
has_following_position = bool(results) # len(results) > len(self.page)
|
|
109
|
+
|
|
110
|
+
# DF: contrary to DRF's implementation we always provide the "next" link as there may appear new records after
|
|
111
|
+
# the last one read
|
|
112
|
+
following_position = self._get_position_from_instance(results[-1], self.ordering) if results else None
|
|
113
|
+
|
|
114
|
+
if reverse:
|
|
115
|
+
# If we have a reverse queryset, then the query ordering was in reverse
|
|
116
|
+
# so we need to reverse the items again before returning them to the user.
|
|
117
|
+
self.page = list(reversed(self.page))
|
|
118
|
+
|
|
119
|
+
# Determine next and previous positions for reverse cursors.
|
|
120
|
+
self.has_next = (current_position is not None) or (offset > 0)
|
|
121
|
+
self.has_previous = has_following_position
|
|
122
|
+
if self.has_next:
|
|
123
|
+
self.next_position = current_position
|
|
124
|
+
if self.has_previous:
|
|
125
|
+
self.previous_position = following_position
|
|
126
|
+
else:
|
|
127
|
+
# Determine next and previous positions for forward cursors.
|
|
128
|
+
self.has_next = has_following_position
|
|
129
|
+
self.has_previous = (current_position is not None) or (offset > 0)
|
|
130
|
+
if self.has_next:
|
|
131
|
+
self.next_position = following_position
|
|
132
|
+
if self.has_previous:
|
|
133
|
+
self.previous_position = current_position
|
|
134
|
+
|
|
135
|
+
# Display page controls in the browsable API if there is more
|
|
136
|
+
# than one page.
|
|
137
|
+
if (self.has_previous or self.has_next) and self.template is not None:
|
|
138
|
+
self.display_page_controls = True
|
|
139
|
+
|
|
140
|
+
return self.page
|
|
141
|
+
|
|
142
|
+
def _get_position_from_instance(self, instance, ordering):
|
|
143
|
+
def process_field(idx):
|
|
144
|
+
field_name = ordering[idx].lstrip("-")
|
|
145
|
+
attr = None
|
|
146
|
+
if isinstance(instance, dict):
|
|
147
|
+
attr = instance[field_name]
|
|
148
|
+
else:
|
|
149
|
+
field_name_list = field_name.split("__")
|
|
150
|
+
for fn in field_name_list:
|
|
151
|
+
attr = getattr(instance if attr is None else attr, fn)
|
|
152
|
+
return field_name, self.field_to_representation(attr)
|
|
153
|
+
|
|
154
|
+
return json.dumps({k: v for k, v in map(lambda idx: process_field(idx), range(len(ordering)))})
|
|
155
|
+
|
|
156
|
+
def field_to_representation(self, value):
|
|
157
|
+
"""
|
|
158
|
+
Converts field value to something that will be later understood by QuerySet.filter()
|
|
159
|
+
Question: why does DRF not do this? is str(value) always enough?
|
|
160
|
+
"""
|
|
161
|
+
if isinstance(value, datetime.timedelta):
|
|
162
|
+
return str(value.total_seconds())
|
|
163
|
+
elif value is None:
|
|
164
|
+
return self.NONE_VALUE
|
|
165
|
+
return str(value)
|
|
166
|
+
|
|
167
|
+
def field_to_python(self, queryset, order_attr, value):
|
|
168
|
+
if order_attr in queryset.query.annotations:
|
|
169
|
+
field = queryset.query.annotations.get(order_attr).field
|
|
170
|
+
else:
|
|
171
|
+
# noinspection PyProtectedMember
|
|
172
|
+
field = queryset.model._meta.get_field(order_attr)
|
|
173
|
+
if isinstance(field, DurationField):
|
|
174
|
+
return datetime.timedelta(seconds=float(value))
|
|
175
|
+
return value
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from django.core.files.storage import FileSystemStorage
|
|
9
|
+
from django.core.files.uploadedfile import InMemoryUploadedFile
|
|
10
|
+
from django.http import HttpResponse, JsonResponse
|
|
11
|
+
from django.utils.translation import gettext as __
|
|
12
|
+
from rest_framework import status
|
|
13
|
+
from rest_framework.exceptions import ValidationError
|
|
14
|
+
|
|
15
|
+
UPLOADED_FILE_NAME_TIMESTAMP_SEPARATOR = "_timestamp_"
|
|
16
|
+
UPLOADED_FILE_NAME_UUID_SEPARATOR = "_uuid_"
|
|
17
|
+
UPLOADED_FILE_TMP_LOCATION = "df_tmp_files"
|
|
18
|
+
|
|
19
|
+
preuploaded_fs = FileSystemStorage(location=f"{settings.MEDIA_ROOT}/{UPLOADED_FILE_TMP_LOCATION}")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def preupload_file(request):
|
|
23
|
+
# if not DYNAMICFORMS.allow_anonymous_user_to_preupload_files and not request.user.is_authenticated:
|
|
24
|
+
# return HttpResponse(status=status.HTTP_401_UNAUTHORIZED)
|
|
25
|
+
if request.method == "POST":
|
|
26
|
+
uploaded_file: Optional[InMemoryUploadedFile] = request.FILES.get("file", None)
|
|
27
|
+
if not uploaded_file:
|
|
28
|
+
raise ValidationError(dict(file=["required"]))
|
|
29
|
+
file_identifier: str = str(uuid4())
|
|
30
|
+
try:
|
|
31
|
+
file_extension: str = pathlib.Path(uploaded_file.name).suffix
|
|
32
|
+
file_name: str = uploaded_file.name.replace(file_extension, "")
|
|
33
|
+
preuploaded_fs.save(
|
|
34
|
+
f"{file_name}{UPLOADED_FILE_NAME_UUID_SEPARATOR}{file_identifier}"
|
|
35
|
+
f"{UPLOADED_FILE_NAME_TIMESTAMP_SEPARATOR}{int(time.time())}"
|
|
36
|
+
f"{file_extension}",
|
|
37
|
+
uploaded_file,
|
|
38
|
+
)
|
|
39
|
+
return JsonResponse(dict(identifier=file_identifier))
|
|
40
|
+
except Exception:
|
|
41
|
+
return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR, content=__("File upload failed").encode())
|
|
42
|
+
return HttpResponse(status=status.HTTP_501_NOT_IMPLEMENTED)
|
dynamicforms/progress.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from django.core.cache import cache
|
|
2
|
+
from django.http import HttpResponse
|
|
3
|
+
from rest_framework.renderers import JSONRenderer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_progress_value(request):
|
|
7
|
+
"""
|
|
8
|
+
Returns operations progress to HTTP client
|
|
9
|
+
|
|
10
|
+
:param request:
|
|
11
|
+
:return: Progress in percentages (0 - 100) if progress is set else None
|
|
12
|
+
"""
|
|
13
|
+
progress_key = get_progress_key(request)
|
|
14
|
+
value = None
|
|
15
|
+
if progress_key is not None:
|
|
16
|
+
cache_value = cache.get("df_progress.%s" % progress_key)
|
|
17
|
+
if cache_value is not None:
|
|
18
|
+
value = "%.2f" % cache.get("df_progress.%s" % progress_key)
|
|
19
|
+
|
|
20
|
+
return HttpResponse(
|
|
21
|
+
JSONRenderer().render(dict(value=value, comment=cache.get("df_progress_comment.%s" % progress_key))),
|
|
22
|
+
content_type="application/json",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_progress_key(request):
|
|
27
|
+
"""
|
|
28
|
+
Gets progress/operation key under which operation progress will be stored in servers cache.
|
|
29
|
+
:param request:
|
|
30
|
+
:return: Progress key or None if there is no x_df_timestamp in request header
|
|
31
|
+
"""
|
|
32
|
+
timestamp = request.META.get("HTTP_X_DF_TIMESTAMP", request.GET.get("x_df_timestamp", None))
|
|
33
|
+
if timestamp is not None:
|
|
34
|
+
return "%s|%s" % (timestamp, request.session.session_key)
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def set_progress_value(progress_key, value):
|
|
39
|
+
"""
|
|
40
|
+
Updates operations progress
|
|
41
|
+
|
|
42
|
+
:param progress_key: Key under which operations progress is stored
|
|
43
|
+
:param value: Progress in percentages (0.00 - 100.00)
|
|
44
|
+
:return:
|
|
45
|
+
"""
|
|
46
|
+
if progress_key is not None:
|
|
47
|
+
cache.set("df_progress.%s" % progress_key, value)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def add_progress_value(progress_key, value):
|
|
51
|
+
"""
|
|
52
|
+
Updates operations progress by adding to existing progress value
|
|
53
|
+
|
|
54
|
+
:param progress_key: Key under which operations progress is stored
|
|
55
|
+
:param value: Progress in percentages (0.00 - 100.00)
|
|
56
|
+
:return:
|
|
57
|
+
"""
|
|
58
|
+
if progress_key is not None:
|
|
59
|
+
cache.set("df_progress.%s" % progress_key, cache.get("df_progress.%s" % progress_key, 0) + value)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def set_progress_comment(progress_key, value):
|
|
63
|
+
"""
|
|
64
|
+
Updates operations comment
|
|
65
|
+
|
|
66
|
+
:param progress_key: Key under which operations progress is stored
|
|
67
|
+
:param value: comment
|
|
68
|
+
:return:
|
|
69
|
+
"""
|
|
70
|
+
if progress_key is not None:
|
|
71
|
+
cache.set("df_progress_comment.%s" % progress_key, value)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .template_render import ComponentDefRenderer # noqa: F401
|
dynamicforms/routers.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import Optional, Set
|
|
2
|
+
|
|
3
|
+
from rest_framework.routers import DefaultRouter, Route
|
|
4
|
+
|
|
5
|
+
from dynamicforms.viewsets import SingleRecordViewSet
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DFRouter(DefaultRouter):
|
|
9
|
+
single_record_registrations: Set[int] = set()
|
|
10
|
+
|
|
11
|
+
def register_single_record(self, prefix: str, viewset: SingleRecordViewSet, basename: Optional[str] = None):
|
|
12
|
+
"""
|
|
13
|
+
Register a single-record viewset with the router.
|
|
14
|
+
"""
|
|
15
|
+
self.single_record_registrations.add(id(viewset))
|
|
16
|
+
self.register(prefix, viewset, basename)
|
|
17
|
+
|
|
18
|
+
def get_routes(self, viewset):
|
|
19
|
+
if id(viewset) in self.single_record_registrations:
|
|
20
|
+
# these routes are copied from SimpleRouter, but modified so that they don't ask for a parameter
|
|
21
|
+
return [
|
|
22
|
+
Route(
|
|
23
|
+
url=r"^{prefix}{trailing_slash}$",
|
|
24
|
+
mapping={
|
|
25
|
+
"post": "create",
|
|
26
|
+
"get": "retrieve",
|
|
27
|
+
"put": "update",
|
|
28
|
+
"patch": "partial_update",
|
|
29
|
+
"delete": "destroy",
|
|
30
|
+
},
|
|
31
|
+
name="{basename}-detail",
|
|
32
|
+
detail=True,
|
|
33
|
+
initkwargs={"suffix": "Instance"},
|
|
34
|
+
),
|
|
35
|
+
]
|
|
36
|
+
return super().get_routes(viewset)
|