django-advanced-modals 1.0.0__tar.gz
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.
- django_advanced_modals-1.0.0/LICENSE +21 -0
- django_advanced_modals-1.0.0/MANIFEST.in +3 -0
- django_advanced_modals-1.0.0/PKG-INFO +531 -0
- django_advanced_modals-1.0.0/django_advanced_modals.egg-info/PKG-INFO +531 -0
- django_advanced_modals-1.0.0/django_advanced_modals.egg-info/SOURCES.txt +177 -0
- django_advanced_modals-1.0.0/django_advanced_modals.egg-info/dependency_links.txt +1 -0
- django_advanced_modals-1.0.0/django_advanced_modals.egg-info/requires.txt +4 -0
- django_advanced_modals-1.0.0/django_advanced_modals.egg-info/top_level.txt +1 -0
- django_advanced_modals-1.0.0/django_modals/__init__.py +0 -0
- django_advanced_modals-1.0.0/django_modals/apps.py +5 -0
- django_advanced_modals-1.0.0/django_modals/datatables.py +62 -0
- django_advanced_modals-1.0.0/django_modals/decorators.py +76 -0
- django_advanced_modals-1.0.0/django_modals/fields.py +161 -0
- django_advanced_modals-1.0.0/django_modals/form_helpers.py +76 -0
- django_advanced_modals-1.0.0/django_modals/forms.py +340 -0
- django_advanced_modals-1.0.0/django_modals/helper.py +170 -0
- django_advanced_modals-1.0.0/django_modals/includes.py +64 -0
- django_advanced_modals-1.0.0/django_modals/messages.py +44 -0
- django_advanced_modals-1.0.0/django_modals/mixins.py +24 -0
- django_advanced_modals-1.0.0/django_modals/modal_upload.py +37 -0
- django_advanced_modals-1.0.0/django_modals/modals/__init__.py +6 -0
- django_advanced_modals-1.0.0/django_modals/modals/datatables.py +42 -0
- django_advanced_modals-1.0.0/django_modals/modals/form_modals.py +273 -0
- django_advanced_modals-1.0.0/django_modals/modals/formset.py +112 -0
- django_advanced_modals-1.0.0/django_modals/modals/json_mixin.py +108 -0
- django_advanced_modals-1.0.0/django_modals/modals/modals.py +234 -0
- django_advanced_modals-1.0.0/django_modals/modals/multi_form.py +239 -0
- django_advanced_modals-1.0.0/django_modals/modals/multi_form_formsets.py +121 -0
- django_advanced_modals-1.0.0/django_modals/model_fields/__init__.py +0 -0
- django_advanced_modals-1.0.0/django_modals/model_fields/colour.py +18 -0
- django_advanced_modals-1.0.0/django_modals/processes.py +40 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/css/bootstrap4-toggle.min.css +10 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/css/modals.css +486 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/css/select2.min.css +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/jquery-month-picker/css/MonthPicker.min.css +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/jquery-month-picker/js/MonthPicker.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/jquery_ui_limited/css/images/ui-icons_444444_256x240.png +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/jquery_ui_limited/css/images/ui-icons_555555_256x240.png +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/jquery_ui_limited/css/images/ui-icons_777620_256x240.png +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/jquery_ui_limited/css/images/ui-icons_777777_256x240.png +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/jquery_ui_limited/css/images/ui-icons_cc0000_256x240.png +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/jquery_ui_limited/css/images/ui-icons_ffffff_256x240.png +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/jquery_ui_limited/css/jquery-ui.min.css +7 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/jquery_ui_limited/js/jquery-ui.min.js +6 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/js/bootstrap4-toggle.min.js +11 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/js/bootstrap4-toggle.min.js.map +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/js/jquery.formset.js +289 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/js/modals.js +464 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/js/modals.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/js/modals_legacy.js +552 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/js/select2.min.js +2 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/js/widget_select2.js +149 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/pickr/css/classic.min.css +3 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/pickr/css/monolith.min.css +3 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/pickr/css/nano.min.css +3 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/pickr/js/pickr.es5.min.js +4 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/pickr/js/pickr.es5.min.js.map +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/pickr/js/pickr.min.js +3 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/pickr/js/pickr.min.js.map +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/jquery.tinymce.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/langs/readme.md +3 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/license.txt +504 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/advlist/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/anchor/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/autolink/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/autoresize/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/autosave/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/bbcode/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/charmap/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/code/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/codesample/css/prism.css +138 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/codesample/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/colorpicker/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/contextmenu/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/directionality/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-cool.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-cry.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-embarassed.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-foot-in-mouth.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-frown.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-innocent.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-kiss.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-laughing.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-money-mouth.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-sealed.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-smile.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-surprised.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-tongue-out.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-undecided.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-wink.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/img/smiley-yell.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/emoticons/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/fullpage/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/fullscreen/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/help/img/logo.png +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/help/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/hr/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/image/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/imagetools/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/importcss/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/insertdatetime/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/legacyoutput/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/link/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/lists/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/media/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/nonbreaking/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/noneditable/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/pagebreak/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/paste/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/preview/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/print/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/save/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/searchreplace/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/spellchecker/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/tabfocus/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/table/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/template/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/textcolor/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/textpattern/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/toc/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/visualblocks/css/visualblocks.css +154 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/visualblocks/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/visualchars/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/plugins/wordcount/plugin.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/content.inline.min.css +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/content.min.css +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/content.mobile.min.css +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/fonts/tinymce-mobile.woff +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/fonts/tinymce-small.eot +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/fonts/tinymce-small.svg +63 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/fonts/tinymce-small.ttf +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/fonts/tinymce-small.woff +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/fonts/tinymce.eot +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/fonts/tinymce.svg +132 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/fonts/tinymce.ttf +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/fonts/tinymce.woff +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/img/anchor.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/img/loader.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/img/object.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/img/trans.gif +0 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/skin.min.css +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/skins/lightgray/skin.mobile.min.css +2 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/themes/inlite/theme.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/themes/mobile/theme.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/themes/modern/theme.min.js +1 -0
- django_advanced_modals-1.0.0/django_modals/static/django_modals/tinymce/tinymce.min.js +2 -0
- django_advanced_modals-1.0.0/django_modals/task_modals.py +65 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/blank_page_form.html +27 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/datatables/datatable.html +24 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/fields/label_checkbox.html +32 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/formset/prepended_appended_text.html +51 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/formset/table_inline_formset.html +56 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/message.html +8 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/modal_base.html +48 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/modal_formset_base.html +23 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/modal_lazy_body.html +4 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/modal_menu.html +13 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/multi_form/table_formset.html +30 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/multi_form.html +7 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/scratchpad.html +26 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/widgets/colour_picker.html +69 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/widgets/colour_picker_append.html +61 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/widgets/datepicker.html +18 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/widgets/month_picker.html +14 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/widgets/select2.html +48 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/widgets/tinymce.html +19 -0
- django_advanced_modals-1.0.0/django_modals/templates/django_modals/widgets/toggle.html +8 -0
- django_advanced_modals-1.0.0/django_modals/templates/example_views/datatables.html +15 -0
- django_advanced_modals-1.0.0/django_modals/templatetags/__init__.py +0 -0
- django_advanced_modals-1.0.0/django_modals/templatetags/modal_tags.py +30 -0
- django_advanced_modals-1.0.0/django_modals/url_helper.py +20 -0
- django_advanced_modals-1.0.0/django_modals/widgets/colour_picker.py +30 -0
- django_advanced_modals-1.0.0/django_modals/widgets/jquery_datepicker.py +8 -0
- django_advanced_modals-1.0.0/django_modals/widgets/month_picker.py +8 -0
- django_advanced_modals-1.0.0/django_modals/widgets/select2.py +168 -0
- django_advanced_modals-1.0.0/django_modals/widgets/widgets.py +11 -0
- django_advanced_modals-1.0.0/pyproject.toml +37 -0
- django_advanced_modals-1.0.0/readme.md +510 -0
- django_advanced_modals-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Ian Jones
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-advanced-modals
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Django app to implement Bootstrap nested modals
|
|
5
|
+
Author-email: Ian Jones <ian@cobi.co.uk>
|
|
6
|
+
Maintainer: Thomas Turner
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/django-advance-utils/django-advanced-modals
|
|
9
|
+
Project-URL: Upstream, https://github.com/jonesim/django-modals
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.6
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: ajax-advanced-helpers>=1.0.0
|
|
17
|
+
Requires-Dist: django-advanced-menus>=1.0.0
|
|
18
|
+
Requires-Dist: django-crispy-forms
|
|
19
|
+
Requires-Dist: make-html
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
[](https://pypi.org/project/django-advanced-modals/)
|
|
23
|
+
|
|
24
|
+
# django-advanced-modals
|
|
25
|
+
|
|
26
|
+
Bootstrap nested modals with AJAX form handling for Django. Modals are class-based views — they handle GET (render the modal) and POST (process buttons/forms) via AJAX, with responses returned as JSON command arrays.
|
|
27
|
+
|
|
28
|
+
The [django-advance-utils](https://github.com/django-advance-utils) line of Ian Jones's
|
|
29
|
+
[django-nested-modals](https://github.com/jonesim/django-modals), forked so that releases can be cut as
|
|
30
|
+
the downstream libraries and django-advanced-report-builder need them. The Python package is still
|
|
31
|
+
`django_modals`, so existing imports and `INSTALLED_APPS` entries do not change; only the pip name
|
|
32
|
+
does. It depends on [ajax-advanced-helpers](https://github.com/django-advance-utils/ajax-advanced-helpers)
|
|
33
|
+
and [django-advanced-menus](https://github.com/django-advance-utils/django-advanced-menus).
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install django-advanced-modals
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Add to `INSTALLED_APPS`:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
INSTALLED_APPS = [
|
|
45
|
+
...
|
|
46
|
+
'bootstrap_modals',
|
|
47
|
+
]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Add to your base template:
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<script src="{% static 'django_modals/js/modals.js' %}"></script>
|
|
54
|
+
<link rel="stylesheet" type="text/css" href="{% static 'django_modals/css/modals.css' %}"/>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
### 1. Define a modal view
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
# views.py
|
|
65
|
+
from django_modals.modals import ModelFormModal
|
|
66
|
+
|
|
67
|
+
class CompanyModal(ModelFormModal):
|
|
68
|
+
model = Company
|
|
69
|
+
form_fields = ['name', 'active']
|
|
70
|
+
modal_title = ['New Company', 'Edit Company']
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Wire up a URL
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# urls.py
|
|
77
|
+
path('modal/company/<slug:slug>/', CompanyModal.as_view(), name='company_modal'),
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Trigger it from a template
|
|
81
|
+
|
|
82
|
+
```django
|
|
83
|
+
{% load modal_tags %}
|
|
84
|
+
{% show_modal 'company_modal' 'pk-1' %} {# edit pk=1 #}
|
|
85
|
+
{% show_modal 'company_modal' 'pk-0' %} {# create new #}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Modal Types
|
|
91
|
+
|
|
92
|
+
### `Modal` — simple content modal
|
|
93
|
+
|
|
94
|
+
Use for messages, confirmations, or any custom HTML content.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from django_modals.modals import Modal
|
|
98
|
+
from django_modals.helper import modal_button
|
|
99
|
+
|
|
100
|
+
class InfoModal(Modal):
|
|
101
|
+
modal_title = 'Information'
|
|
102
|
+
|
|
103
|
+
def modal_content(self):
|
|
104
|
+
return '<p>This action cannot be undone.</p>'
|
|
105
|
+
|
|
106
|
+
def get_modal_buttons(self):
|
|
107
|
+
return [modal_button('OK', 'close', 'btn-success')]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `TemplateModal` — modal from a template file
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from django_modals.modals import TemplateModal
|
|
114
|
+
|
|
115
|
+
class HelpModal(TemplateModal):
|
|
116
|
+
modal_title = 'Help'
|
|
117
|
+
modal_template = 'myapp/help_modal.html'
|
|
118
|
+
|
|
119
|
+
def modal_context(self):
|
|
120
|
+
return {'section': self.slug.get('section', 'general')}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `FormModal` — non-model form
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from django_modals.modals import FormModal
|
|
127
|
+
|
|
128
|
+
class ContactModal(FormModal):
|
|
129
|
+
form_class = ContactForm
|
|
130
|
+
modal_title = 'Contact Us'
|
|
131
|
+
|
|
132
|
+
def form_valid(self, form):
|
|
133
|
+
send_email(form.cleaned_data)
|
|
134
|
+
return self.command_response('reload')
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `ModelFormModal` — CRUD modal for a model
|
|
138
|
+
|
|
139
|
+
The most common modal type. Automatically handles create, edit, view, and delete.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from django_modals.modals import ModelFormModal
|
|
143
|
+
from django_modals.processes import PERMISSION_AUTHENTICATED, PERMISSION_STAFF
|
|
144
|
+
|
|
145
|
+
class PersonModal(ModelFormModal):
|
|
146
|
+
model = Person
|
|
147
|
+
form_fields = ['first_name', 'surname', 'email', 'company']
|
|
148
|
+
modal_title = ['New Person', 'Edit Person', 'View Person']
|
|
149
|
+
permission_create = PERMISSION_AUTHENTICATED
|
|
150
|
+
permission_edit = PERMISSION_AUTHENTICATED
|
|
151
|
+
permission_delete = PERMISSION_STAFF
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### `MultiFormModal` — multiple independent forms in one modal
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from django_modals.modals import MultiFormModal, MultiForm
|
|
158
|
+
|
|
159
|
+
class NewAccountModal(MultiFormModal):
|
|
160
|
+
modal_title = 'Create Account'
|
|
161
|
+
forms = [
|
|
162
|
+
MultiForm(Company, ['name', 'active']),
|
|
163
|
+
MultiForm(Person, ['first_name', 'surname', 'email']),
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
def forms_valid(self, valid_forms):
|
|
167
|
+
company = valid_forms['CompanyForm'].save()
|
|
168
|
+
person = valid_forms['PersonForm']
|
|
169
|
+
person.instance.company = company
|
|
170
|
+
person.save()
|
|
171
|
+
return self.command_response('reload')
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## URL Slug System
|
|
177
|
+
|
|
178
|
+
State is passed via a single URL slug encoded as alternating key-value pairs. The slug is parsed into `self.slug` dict inside the modal.
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
pk-1 → {'pk': '1'} edit pk=1
|
|
182
|
+
pk-0 → {} create new
|
|
183
|
+
pk-1-modal-view → {'pk': '1', ...} view (read-only)
|
|
184
|
+
pk-1-modal-viewedit → {'pk': '1', ...} view with edit button
|
|
185
|
+
pk-1-modal-delete → {'pk': '1', ...} delete confirmation
|
|
186
|
+
section-help → {'section': 'help'} arbitrary key-value
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
In the URL conf, use `<slug:slug>` for standard slugs or `<str:slug>` when values contain uppercase or special characters.
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
urlpatterns = [
|
|
193
|
+
path('modal/person/<slug:slug>/', PersonModal.as_view(), name='person_modal'),
|
|
194
|
+
]
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Processes and Permissions
|
|
200
|
+
|
|
201
|
+
Each modal operation is a *process*. Permissions control who can invoke each process.
|
|
202
|
+
|
|
203
|
+
### Process constants (`django_modals.processes`)
|
|
204
|
+
|
|
205
|
+
| Constant | Value | Description |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| `PROCESS_CREATE` | 0 | Create new object |
|
|
208
|
+
| `PROCESS_EDIT` | 1 | Edit existing object |
|
|
209
|
+
| `PROCESS_VIEW` | 2 | Read-only view |
|
|
210
|
+
| `PROCESS_DELETE` | 3 | Delete object |
|
|
211
|
+
| `PROCESS_EDIT_DELETE` | 4 | Edit with delete button |
|
|
212
|
+
| `PROCESS_VIEW_EDIT` | 5 | Read-only with edit button |
|
|
213
|
+
|
|
214
|
+
### Permission constants
|
|
215
|
+
|
|
216
|
+
| Constant | Description |
|
|
217
|
+
|---|---|
|
|
218
|
+
| `PERMISSION_OFF` | No restriction |
|
|
219
|
+
| `PERMISSION_DISABLE` | Always disabled |
|
|
220
|
+
| `PERMISSION_AUTHENTICATED` | Logged-in users only |
|
|
221
|
+
| `PERMISSION_STAFF` | Staff/superusers only |
|
|
222
|
+
| `PERMISSION_METHOD` | Delegate to `permission(user, process)` method |
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from django_modals.processes import (
|
|
226
|
+
PERMISSION_OFF, PERMISSION_AUTHENTICATED,
|
|
227
|
+
PERMISSION_STAFF, PERMISSION_METHOD
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
class CompanyModal(ModelFormModal):
|
|
231
|
+
model = Company
|
|
232
|
+
form_fields = ['name', 'active']
|
|
233
|
+
permission_create = PERMISSION_AUTHENTICATED
|
|
234
|
+
permission_edit = PERMISSION_AUTHENTICATED
|
|
235
|
+
permission_delete = PERMISSION_STAFF
|
|
236
|
+
permission_view = PERMISSION_OFF
|
|
237
|
+
|
|
238
|
+
# Used when permission_X = PERMISSION_METHOD
|
|
239
|
+
@staticmethod
|
|
240
|
+
def permission(user, process):
|
|
241
|
+
return user.groups.filter(name='editors').exists()
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## ModelFormModal Reference
|
|
247
|
+
|
|
248
|
+
### Class attributes
|
|
249
|
+
|
|
250
|
+
| Attribute | Type | Description |
|
|
251
|
+
|---|---|---|
|
|
252
|
+
| `model` | Model class | Required |
|
|
253
|
+
| `form_fields` | list | Field names to include |
|
|
254
|
+
| `form_class` | Form class | Override auto-generated form |
|
|
255
|
+
| `modal_title` | str or list | Single title, or `[create, edit, view]` |
|
|
256
|
+
| `size` | `'sm'` / `'md'` / `'lg'` | Modal dialog size (default `'lg'`) |
|
|
257
|
+
| `labels` | dict | Field label overrides |
|
|
258
|
+
| `help_texts` | dict | Field help text overrides |
|
|
259
|
+
| `widgets` | dict | Widget overrides |
|
|
260
|
+
| `field_classes` | dict | Form field class overrides |
|
|
261
|
+
| `error_messages` | dict | Error message overrides |
|
|
262
|
+
| `permission_create` | constant | Create permission |
|
|
263
|
+
| `permission_edit` | constant | Edit permission |
|
|
264
|
+
| `permission_view` | constant | View permission |
|
|
265
|
+
| `permission_delete` | constant | Delete permission |
|
|
266
|
+
| `delete_message` | str | Confirmation message for delete |
|
|
267
|
+
| `delete_title` | str | Title for delete confirmation |
|
|
268
|
+
| `focus` | bool | Auto-focus first field (default `True`) |
|
|
269
|
+
| `lazy` | bool | Load modal content via AJAX (default `False`) |
|
|
270
|
+
|
|
271
|
+
### Hook methods
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
class PersonModal(ModelFormModal):
|
|
275
|
+
model = Person
|
|
276
|
+
form_fields = ['first_name', 'surname', 'company']
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
def form_setup(form, **kwargs):
|
|
280
|
+
"""Customise form fields before rendering — runs on GET and invalid POST."""
|
|
281
|
+
form.fields['company'].queryset = Company.objects.filter(active=True)
|
|
282
|
+
|
|
283
|
+
def form_valid(self, form):
|
|
284
|
+
"""Override save logic. Must return a command response."""
|
|
285
|
+
instance = form.save()
|
|
286
|
+
return self.command_response('reload')
|
|
287
|
+
|
|
288
|
+
def post_save(self, created, form):
|
|
289
|
+
"""Runs after save, before the response is built."""
|
|
290
|
+
if created:
|
|
291
|
+
notify_team(form.instance)
|
|
292
|
+
|
|
293
|
+
def object_delete(self):
|
|
294
|
+
"""Runs after the object is deleted."""
|
|
295
|
+
audit_log(self.object)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## MultiFormModal Reference
|
|
301
|
+
|
|
302
|
+
Forms are defined with `MultiForm` instances. Each `MultiForm` accepts the same field-configuration arguments as `ModelFormModal`.
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
MultiForm(
|
|
306
|
+
model,
|
|
307
|
+
fields,
|
|
308
|
+
form_id=None, # Defaults to '<ModelName>Form'
|
|
309
|
+
labels={},
|
|
310
|
+
help_texts={},
|
|
311
|
+
widgets={},
|
|
312
|
+
field_classes={},
|
|
313
|
+
error_messages={},
|
|
314
|
+
)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Override methods on the modal:
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
class AccountModal(MultiFormModal):
|
|
321
|
+
forms = [
|
|
322
|
+
MultiForm(Company, ['name']),
|
|
323
|
+
MultiForm(Person, ['first_name', 'surname']),
|
|
324
|
+
]
|
|
325
|
+
|
|
326
|
+
def form_setup(self, form, **kwargs):
|
|
327
|
+
"""Called for each form individually. Return a layout or None."""
|
|
328
|
+
if form.form_id == 'PersonForm':
|
|
329
|
+
form.fields['surname'].required = True
|
|
330
|
+
return None
|
|
331
|
+
|
|
332
|
+
def get_instances(self, form_id):
|
|
333
|
+
"""Return a model instance for edit mode."""
|
|
334
|
+
if form_id == 'CompanyForm':
|
|
335
|
+
return Company.objects.get(pk=self.slug.get('pk'))
|
|
336
|
+
|
|
337
|
+
def forms_valid(self, valid_forms):
|
|
338
|
+
company = valid_forms['CompanyForm'].save()
|
|
339
|
+
person = valid_forms['PersonForm']
|
|
340
|
+
person.instance.company = company
|
|
341
|
+
person.save()
|
|
342
|
+
return self.command_response('reload')
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Forms
|
|
348
|
+
|
|
349
|
+
### `ModelCrispyForm`
|
|
350
|
+
|
|
351
|
+
The default base form used by `ModelFormModal`. Inherits from `ModelForm` and applies crispy-forms layout automatically. Use it directly when you need a custom `form_class`:
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
from django_modals.forms import ModelCrispyForm
|
|
355
|
+
|
|
356
|
+
class PersonForm(ModelCrispyForm):
|
|
357
|
+
class Meta:
|
|
358
|
+
model = Person
|
|
359
|
+
fields = ['first_name', 'surname', 'email']
|
|
360
|
+
|
|
361
|
+
class PersonModal(ModelFormModal):
|
|
362
|
+
form_class = PersonForm
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### `CrispyForm`
|
|
366
|
+
|
|
367
|
+
For non-model forms with crispy rendering:
|
|
368
|
+
|
|
369
|
+
```python
|
|
370
|
+
from django_modals.forms import CrispyForm
|
|
371
|
+
from django import forms
|
|
372
|
+
|
|
373
|
+
class SearchForm(CrispyForm):
|
|
374
|
+
query = forms.CharField()
|
|
375
|
+
active_only = forms.BooleanField(required=False)
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Advanced field configuration with `FieldEx`
|
|
379
|
+
|
|
380
|
+
Pass a tuple `(field_name, options_dict)` in `form_fields` for per-field configuration:
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
from django_modals.helper import FieldEx
|
|
384
|
+
|
|
385
|
+
class CompanyModal(ModelFormModal):
|
|
386
|
+
model = Company
|
|
387
|
+
form_fields = [
|
|
388
|
+
'name',
|
|
389
|
+
('status', {'label': 'Current Status', 'help_text': 'Active or inactive'}),
|
|
390
|
+
FieldEx('notes', template='custom_field.html'),
|
|
391
|
+
]
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Widgets
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
from django_modals.widgets.select2 import Select2, Select2Multiple
|
|
400
|
+
from django_modals.widgets.colour_picker import ColourPickerWidget
|
|
401
|
+
from django_modals.widgets.datepicker import JQueryDatepickerWidget
|
|
402
|
+
from django_modals.widgets.month_picker import MonthPickerWidget
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Select2
|
|
406
|
+
|
|
407
|
+
```python
|
|
408
|
+
class CompanyModal(ModelFormModal):
|
|
409
|
+
model = Company
|
|
410
|
+
form_fields = ['name', 'tags', 'owner']
|
|
411
|
+
widgets = {
|
|
412
|
+
'tags': Select2Multiple,
|
|
413
|
+
'owner': Select2,
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**AJAX-powered Select2** — define a `select2_<fieldname>` method on the modal to filter results dynamically:
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
def select2_owner(self, search, **kwargs):
|
|
421
|
+
qs = Person.objects.filter(name__icontains=search)
|
|
422
|
+
return [(p.pk, str(p)) for p in qs]
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## Template Tags
|
|
428
|
+
|
|
429
|
+
```django
|
|
430
|
+
{% load modal_tags %}
|
|
431
|
+
|
|
432
|
+
{# Link that opens a modal #}
|
|
433
|
+
{% show_modal 'url_name' 'pk-1' %}
|
|
434
|
+
{% show_modal 'url_name' 'pk-1' css_class='btn btn-primary' label='Edit' %}
|
|
435
|
+
|
|
436
|
+
{# AJAX button that calls a method on the current modal #}
|
|
437
|
+
{% modal_button_method 'Save Draft' 'save_draft' %}
|
|
438
|
+
|
|
439
|
+
{# Delete button #}
|
|
440
|
+
{% modal_delete 'url_name' slug='pk-1' text='Delete' %}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## AJAX Commands
|
|
446
|
+
|
|
447
|
+
Modal button handlers and `form_valid` must return a command response. Commands are processed client-side by `ajax_helpers.process_commands`.
|
|
448
|
+
|
|
449
|
+
```python
|
|
450
|
+
# Close the modal and reload the page
|
|
451
|
+
return self.command_response('reload')
|
|
452
|
+
|
|
453
|
+
# Close the modal only
|
|
454
|
+
return self.command_response('close')
|
|
455
|
+
|
|
456
|
+
# Redirect
|
|
457
|
+
return self.command_response('redirect', url='/dashboard/')
|
|
458
|
+
|
|
459
|
+
# Update a DOM element without closing the modal
|
|
460
|
+
self.add_command('html', selector='#status', html='<span>Saved</span>')
|
|
461
|
+
return self.command_response()
|
|
462
|
+
|
|
463
|
+
# Replace the modal content
|
|
464
|
+
return self.command_response('overwrite_modal', html=rendered_html)
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Custom button handlers are methods named `button_<name>`:
|
|
468
|
+
|
|
469
|
+
```python
|
|
470
|
+
class ReportModal(TemplateModal):
|
|
471
|
+
modal_title = 'Report'
|
|
472
|
+
modal_template = 'report.html'
|
|
473
|
+
|
|
474
|
+
def button_export(self, **kwargs):
|
|
475
|
+
data = generate_csv(self.slug.get('pk'))
|
|
476
|
+
return self.command_response('save_file', filename='report.csv', data=data)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Lazy Loading
|
|
482
|
+
|
|
483
|
+
Set `lazy = True` on a modal to defer content loading until the modal is opened. Useful for slow queries:
|
|
484
|
+
|
|
485
|
+
```python
|
|
486
|
+
class HeavyReportModal(TemplateModal):
|
|
487
|
+
lazy = True
|
|
488
|
+
modal_title = 'Report'
|
|
489
|
+
modal_template = 'report.html'
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## JSON Field Mixin
|
|
495
|
+
|
|
496
|
+
`JsonFieldMixin` expands a `JSONField` into individually editable typed form fields:
|
|
497
|
+
|
|
498
|
+
```python
|
|
499
|
+
from django_modals.modals import ModelFormModal, JsonFieldMixin
|
|
500
|
+
from django import forms
|
|
501
|
+
|
|
502
|
+
class CompanyModal(JsonFieldMixin, ModelFormModal):
|
|
503
|
+
model = Company
|
|
504
|
+
form_fields = ['name']
|
|
505
|
+
json_field = 'extra'
|
|
506
|
+
json_field_config = {
|
|
507
|
+
'notes': (forms.CharField, {'required': False, 'label': 'Notes'}),
|
|
508
|
+
'priority': (forms.ChoiceField, {
|
|
509
|
+
'choices': [('low', 'Low'), ('high', 'High')],
|
|
510
|
+
'required': False,
|
|
511
|
+
}),
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
The modal renders an "Add field" dropdown; selecting a key adds that field to the form. On save, all values are packed back into the JSON column.
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Development / Example Project
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
docker-compose up
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Django app runs at `http://localhost:8007`. See `django_examples/modal_examples/views/` for working examples of every modal type.
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
# Run tests
|
|
529
|
+
cd django_examples
|
|
530
|
+
python manage.py test django_modals
|
|
531
|
+
```
|