Package: django-frontend-forms
1django-frontend-formsA Django helper app to add editing capabilities to the frontend using modal forms.An accompaningDemo siteprovides:usage instructions and suggestionsa detailed description of the techniques used under the hooda list of working code samples (source:https://github.com/morlandi/django-frontend-forms/tree/master/example/samples)Bases on my previous research as documented here:Editing Django models in the front endContents1django-frontend-forms1.1Installation1.2How to use it1.2.1Basic example1.2.2Dialog methods1.2.3Dialog options1.2.4Dialog notifications1.3Handling form submission1.4Giving a feedback after successful form submission1.5Returning JSON result after form validation1.6Logging in with a modal form1.7Replacing login_required1.8A full, real example for a Django Form submission from a Dialog1.9Editing a Django Model from a Dialog1.10Default dialog layout1.11App Settings1.12“bs4” flavor1.13Utilities (module FrontendForms)1.14Form rendering helpers1.15Datepicker support1.16jQuery MultiSelect support1.17django-select2 support2History2.1v0.3.32.2v0.3.22.3v0.3.12.4v0.3.02.5v0.2.202.6v0.2.192.7v0.2.182.8v0.2.172.9v0.2.162.10v0.2.152.11v0.2.142.12v0.2.132.13v0.2.122.14v0.2.112.15v0.2.102.16v0.2.92.17v0.2.82.18v0.2.72.19v0.2.62.20v0.2.52.21v0.2.42.22v0.2.32.23v0.2.22.24v0.2.12.25v0.2.02.26v0.1.132.27v0.1.122.28v0.1.112.29v0.1.102.30v0.1.92.31v0.1.82.32v0.1.72.33v0.1.62.34v0.1.52.35v0.1.42.36v0.1.32.37v0.1.22.38v0.1.12.39v0.1.02.40v0.0.142.41v0.0.132.42v0.0.122.43v0.0.112.44v0.0.102.45v0.0.92.46v0.0.82.47v0.0.72.48v0.0.62.49v0.0.52.50v0.0.42.51v0.0.32.52v0.0.22.53v0.0.11.1InstallationInstall the package by running:pipinstalldjango-frontend-formsor:pipinstallgit+https://github.com/morlandi/django-frontend-formsIn your settings, add:INSTALLED_APPS=[...'frontend_forms',]Include library’s views mapping (fileurls.py):urlpatterns=[...path('frontend_forms/',include('frontend_forms.urls',namespace='frontend_forms')),...In your base template, include: the default styles, the javascript support,
the javascript messaeg catalog, and optionally the sample HTML template:<linkrel='stylesheet'href="{% static 'frontend_forms/css/frontend_forms.css' %}">{% if USE_VANILLA_JS %}<scriptsrc="{% static 'frontend_forms/js/frontend_forms_vanilla.js' %}"></script>{% else %}<scriptsrc="{% static 'frontend_forms/js/frontend_forms.js' %}"></script>{% endif %}<scriptsrc="{% url 'frontend_forms:javascript-catalog' %}"></script>{% include 'frontend_forms/dialogs.html' %}1.2How to use itFollow the intructions given on theDemo site1.2.1Basic exampleIn the following example, we build a Dialog() object providing some custom options;
then, we use it to open a modal dialog and load it from the specified url.For demonstration purposes, we also subscribe the ‘created’ notification.<scriptlanguage="javascript">$(document).ready(function(){dialog1=newDialog({html:'<h1>Loading ...</h1>',url:'{% url 'frontend:j_object' %}',width:'400px',min_height:'200px',title:'<i class="fa fa-calculator"></i> Selezione Oggetto',footer_text:'testing dialog ...',enable_trace:true,callback:function(event_name,dialog,params){switch(event_name){case"created":console.log('Dialog created: dialog=%o, params=%o',dialog,params);break;}}});});</script><ahref="#"class="btn btn-primary pull-right"onclick="dialog1.open(event); return false;"><iclass="fa fa-plus-circle"></i>Test Popup</a>1.2.2Dialog methodsMethodEffectsconstructor(options={})Seeoptionslist belowopen(event=null, show=true)Open the dialogthe dialog body will be immediately loaded with static content provided by option “html”then the dialog is shown (unless the “show” parameter is false)finally, dynamic content will be loaded from remote address provided by option “url” (if supplied)if successfull, a ‘loaded.dialog’ event is fired; you can use it to perform any action required after loadingclose()Close (hide) the dialogshow()Make the dialog visible1.2.3Dialog optionsOptionDefault valueNotesdialog_selector‘#dialog_generic’The selector for HTML dialog templateopen_eventnullUsed to “remember” the event which triggered Dialog openinghtml‘’Static content to display in dialog bodyurl‘’Optional url to retrieve dialog content via Ajaxwidthnullmin_widthnullmax_widthnullheightnullmin_heightnullmax_heightnullbutton_save_label‘Save’button_save_initially_hiddenfalseWill be shown after form renderingbutton_close_label‘Cancel’title‘’subtitle‘’footer_text‘’enable_tracefalseshow notifications in debug consolecallbacknulla callback to receive eventsautofocus_first_visible_inputtrueUnspecified options will be retrieved from corresponding HTML attributes on the
element which fires the dialog opening;
for example:<ahref="{% url 'frontend:whatever' object.id %}"data-title="My title"data-subtitle="My Subtitle"onclick="new Dialog().open(event); return false;">Open</a>OptionHTML attributeurlhrefhtmldata-htmlwidthdata-widthmin_widthdata-min-widthmax_widthdata-max-widthheightdata-heightmin_heightdata-min-heightmax_heightdata-max-heightbutton_save_labeldata-button-save-labelbutton_close_labeldata-button-close-labeltitledata-titlesubtitledata-subtitlefooter_textdata-footer-text1.2.4Dialog notificationsevent_nameparamscreatedoptionsclosedinitializedshownloadingurlloadedurl, dataloading_failedjqXHR, textStatus, errorThrownopensubmittingmethod, url, datasubmission_failuremethod, url, datasubmittedmethod, url, dataDuring it’s lifetime, the Dialog will notify all interesting events to the caller,
provided he supplies a suitable callback in the contructor:self.options.callback(event_name, dialog, params)Example:dialog1=newDialog({...callback:function(event_name,dialog,params){console.log('event_name: %o, dialog: %o, params: %o',event_name,dialog,params);}});Result:event_name: "created", dialog: Dialog {options: {…}, element: …}, params: {options: {…}}
event_name: "initialized", dialog: Dialog {options: {…}, element: …}, params: {}
event_name: "open", dialog: Dialog {options: {…}, element: …}, params: {}
event_name: "shown", dialog: Dialog {options: {…}, element: …}, params: {}
event_name: "loading", dialog: Dialog {options: {…}, element: …}, params: {url: "/admin_ex/popup/"}
event_name: "loaded", dialog: Dialog {options: {…}, element: …}, params: {url: "/admin_ex/popup/"}
event_name: "submitting", dialog: Dialog {options: {…}, element: …}, params: {method: "post", url: "/admin_ex/popup/", data: "text=&number=aaa"}
event_name: "submitted", dialog: Dialog {options: {…}, element: …}, params: {method: "post", url: "/admin_ex/popup/", data: "text=111&number=111"}
event_name: "closed", dialog: Dialog {options: {…}, element: …}, params: {}You can also trace all events in the console setting the boolean flagenable_trace.1.3Handling form submissionWhen a form submission is involved, the modal life cycle has to be modified as follows:First and foremost, we need toprevent the form from performing its default submit.If not, after submission we’ll be redirected to the form action, outside the context
of the dialog.We’ll do this binding to the form’s submit event, where we’ll serialize the form’s
content and sent it to the view for validation via an Ajax call.Then, upon a successufull response from the server,we’ll need to further investigate
the HTML received:if it contains any field error, the form did not validate successfully,
so we update the modal body with the new form and its errorsotherwise, user interaction is completed, and we can finally close the modaldjango-frontend-forms, upon detecting a form in the content downloaded from the server,
already takes care of all these needs automatically, and keeps refreshing the modal
after each submission until the form validation succeedes.Thus, you can safely forget about all these technicalities
and just include a form in the rendered response as you would in any common form-processing view:urlpatterns=[...path('j/edit_profile/',ajax.edit_profile,name='j_edit_profile'),...]fromdjangoimportformsclassUserProfileForm(forms.ModelForm):classMeta:model=Profilefields=['whatever',...]fromdjango.core.exceptionsimportPermissionDeniedfromdjango.contrib.auth.decoratorsimportlogin_requiredfromdjango.views.decorators.cacheimportnever_cachefromdjango.shortcutsimportrender@login_required@never_cachedefedit_profile(request):is_ajax_request=request.accepts("application/json")ifnotis_ajax_request:raisePermissionDeniedtemplate_name='frontend_forms/generic_form_inner.html'ifrequest.method=='POST':form=UserProfileForm(data=request.POST,instance=request.user.profile)ifform.is_valid():form.save()else:form=UserProfileForm(instance=request.user.profile)returnrender(request,template_name,{'form':form,'object':request.user,# unused, but armless})and later on:<ahref="{% url 'frontend:j_edit_profile' %}"class="btn btn-info"data-title="My title"data-subtitle="My Subtitle"data-width="50%"data-height="50%"onclick="new Dialog().open(event); return false;">Open</a>or, to keep more control over the modal life cycle:<aclass="btn btn-info"href="#"onclick="dialog_edit_profile.open(); return false;"><iclass="fa fa-user"></i>{% trans 'Edit Profile ...' %}</a><scriptlanguage="javascript">$(document).ready(function(){dialog_edit_profile=newDialog({html:'<div>{% trans "Please wait" %} ...</div>',url:'{% url "frontend:j_edit_profile" %}',width:'400px',min_height:'200px',title:'<i class="fa fa-user"></i>&nbsp;&nbsp;{% trans "Edit Profile" %} ...',callback:function(event_name,dialog,params){switch(event_name){case"loaded":dialog.element.find('.django-select2').djangoSelect2({// "dropdownParent" is required for Bootstrap; see:// https://select2.org/troubleshooting/common-problems#select2-does-not-function-properly-when-i-use-it-inside-a-bootstdropdownParent:dialog.element,width:'style'});break;case"submitted":FrontendForms.hide_mouse_cursor();FrontendForms.reload_page(true);break;}}});});</script>1.4Giving a feedback after successful form submissionSometimes, you might want to notify the user after successful form submission.To obtain this, all you have to do, after the form has been validated and saved,
is to return an HTML fragment with no forms in it; in this case:the popup will not closethe “save” button will be hiddenthus giving to the user a chance to read your feedback.defform_validation_with_feedback(request):assertrequest.is_ajax()ifrequest.method=='POST':form=MyForm(data=request.POST)ifform.is_valid():form.save()returnHttpResponse("<h1>Great !</h1> Your form has been validated")else:form=MyForm()returnrender(request,"my_form.html",{'form':form,})1.5Returning JSON result after form validation...ifrequest.method=='POST':form=MyForm(data=request.POST)ifform.is_valid():form.save()ifnotis_ajax(request):messages.info(request,"Form has been validated")else:returnJsonResponse(form.cleaned_data)1.6Logging in with a modal formIf you’re trying to minimize page switching and reduce navigation in your frontend,
why not provide a modal window for login as well ?The library contains a login view adapted from the standard (function based) Django
login view, which can be used for either a standalone HTML page or in a Dialog.For example:<aid="login_with_dialog"href="{% url 'frontend_forms:login' %}"><iclass="fa fa-sign-in"></i>Login</a><scriptlanguage="javascript">$(document).ready(function(){$('#login_with_dialog').on('click',function(event){event.preventDefault();vartarget=$(event.target);varurl=target.attr('href');varlogged_in=false;varlogin_dialog=newDialog({url:url,width:'400px',min_height:'200px',title:'<i class="fa fa-sign-in"></i> Login ...',button_save_label:"Login",button_close_label:"Close",callback:function(event_name,dialog,params){switch(event_name){case"submitted":logged_in=true;break;case"closed":if(logged_in){FrontendForms.redirect('/',true);}break;}}});login_dialog.open(event);});});</script>You can customize the following templates:frontend_forms/login.htmlfrontend_forms/login_inner.htmlfrontend_forms/login_successful_message.html1.7Replacing login_requiredA decorator suitable for modal forms is provided to replace login_required():fromfrontend_forms.decoratorsimportcheck_logged_in@check_logged_in()defmy_view(request,...):...It checks that the user is logged in, showing an error message in place if not.You can customize the following template:frontend_forms/check_logged_in_failed.html1.8A full, real example for a Django Form submission from a DialogWe start by creating a view for form rendering and submission:fileajax.py:importtimefromfrontend_forms.decoratorsimportcheck_logged_infromdjango.views.decorators.cacheimportnever_cachefromdjango.core.exceptionsimportPermissionDeniedfromdjango.httpimportHttpResponseRedirect@check_logged_in()@never_cachedefselect_contract(request):# if settings.DEBUG:#     time.sleep(0.5);ifnotrequest.user.has_perm('backend.view_contract')ornotrequest.is_ajax():raisePermissionDenied#template_name = 'frontend/dialogs/generic_form_inner_with_video.html'template_name='dashboard/dialogs/select_contract.html'object=Noneifrequest.method=='POST':form=SelectContractForm(request=request,data=request.POST)ifform.is_valid():object=form.save(request)ifnotrequest.is_ajax():# reload the pagenext=request.META['PATH_INFO']returnHttpResponseRedirect(next)# if is_ajax(), we just return the validated form, so the modal will closeelse:form=SelectContractForm(request=request)returnrender(request,template_name,{'form':form,'object':object,# unused, but armless})and provide an endpoint to it for ajax call:fileurls.pyfromdjango.urlsimportpathfrom.importajaxapp_name='dashboard'urlpatterns=[...path('j/select_contract/',ajax.select_contract,name='j_select_contract'),...]The Form in this example does a few interesting things:includes some specific assets declaring an inner Media classreceives the request upon constructionuses it to provide specific initial values to the widgetsprovides some specific validations withclean()encapsulates insave()all actions required after successfull submissionfileforms.py:importjsonimportdatetimefromdjangoimportformsfromselectable.formsimportAutoCompleteWidget,AutoCompleteSelectWidget,AutoComboboxSelectWidgetfrombackend.modelsimportContractfromdjango.utils.safestringimportmark_safefrom.lookupsimportContractLookupclassSelectContractForm(forms.Form):contract=forms.CharField(label='Contract',widget=AutoComboboxSelectWidget(ContractLookup,limit=10),required=True,help_text=mark_safe("&nbsp;"),)today=forms.BooleanField(label="Oggi",required=False)date=forms.DateField(widget=forms.DateInput(),label='',required=False)classMedia:css={'screen':('dashboard/css/select_contract_form.css',)}js=('dashboard/js/select_contract_form.js',)def__init__(self,request,*args,**kwargs):super().__init__(*args,**kwargs)self.fields['date'].widget=forms.DateInput(attrs={'class':'datepicker'})assertrequest.user.is_authenticatedandrequest.user.is_activeself.fields['contract'].initial=request.user.contract_attivoself.fields['date'].initial=request.user.data_attivaself.fields['today'].initial=request.user.data_attivaisNonedeflookup_contract(self):try:contract=Contract.objects.get(id=self.cleaned_data['contract'])exceptContract.DoesNotExist:contract=Nonereturncontractdefclean(self):cleaned_data=self.cleaned_dataifnotcleaned_data['today']andnotcleaned_data['date']:raiseforms.ValidationError({'date':'Questo campo è obbligatorio'})returncleaned_datadefsave(self,request):user=request.userassertrequest.user.is_authenticatedandrequest.user.is_activeuser.contract_attivo=self.lookup_contract()ifself.cleaned_data['today']:user.data_attiva=Noneelse:user.data_attiva=self.cleaned_data['date']user.save(update_fields=['contract_attivo','data_attiva',])The javascript and css assets are used for specific needs of this form:functiononChangeToday(event){varcontroller=$('#id_today');varvalue=controller.is(":checked");$('#id_date').prop('disabled',value);$('.field-date .ui-datepicker-trigger').prop('disabled',value);if(value){$('#id_date').datepicker('setDate',null);}}$(document).ready(function(){$('#id_today').on('change',onChangeToday);onChangeToday();});In the template, remember to include the Form’s assets:{% load i18n frontend_forms_tags %}

{{ form.media.css }}<divclass="row"><divclass="col-sm-12"><formaction="{{ action }}"method="post"class="form {{form.form_class}}"novalidateautocomplete="off">{% csrf_token %}

            {% if form.errors or form.non_field_errors %}<pclass="errornote">{% trans 'Please correct the error below.' %}</p>{% endif %}

            {% if form.non_field_errors %}<ulclass="errorlist">{% for error in form.non_field_errors %}<li>{{ error }}</li>{% endfor %}</ul>{% endif %}

            {% for hidden_field in form.hidden_fields %}
                {{ hidden_field }}
            {% endfor %}<fieldset>{% render_form_field form.contract %}<div>Data di riferimento:</div><divclass="data-selection-block">{% render_form_field form.today %}
                    {% render_form_field form.date %}</div></fieldset><inputtype="hidden"name="object_id"value="{{ object.id|default:'' }}"><divclass="form-submit-row"><inputtype="submit"value="Save"/></div></form></div></div>{% if request.is_ajax %}
    {{ form.media.js }}
{% endif %}And finally, the Dialog itself;please note that we use theloadedevent notification to rebind the widgets
after form rendering.{% block extrajs %}<scriptlanguage="javascript">$(document).ready(function(){dialog1=newDialog({dialog_selector:'#dialog_generic',html:'',url:"{% url 'dashboard:j_select_contract' %}",width:'80%',max_width:'400px',min_height:'200px',button_save_label:'Salva',button_close_label:'Annulla',title:'<i class="fa fa-file-o"></i> Selezione Contract',footer_text:'',enable_trace:true,callback:function(event_name,dialog,params){switch(event_name){case"loaded":bindSelectables();dialog.element.find(".datepicker").datepicker({});break;case"submitted":FrontendForms.reload_page(show_layer=true);break;}}});$('.btn-cambia-contract').off().on('click',function(event){event.preventDefault();dialog1.open();})});</script>{% endblock extrajs %}1.9Editing a Django Model from a DialogTODO: TO BE REFINED … AND VERIFIED ;)First of all, we need a view for form rendering and submission.For example:@check_logged_in()@never_cachedefedit_something(request,id_object=None):# if not request.user.has_perm('backend.view_something') or not request.is_ajax():#     raise PermissionDeniedifid_objectisnotNone:object=get_object_or_404(Something,id=id_object)else:object=Nonetemplate_name='frontend_forms/generic_form_inner.html'ifrequest.method=='POST':form=SomethingForm(data=request.POST,instance=object)ifform.is_valid():object=form.save(request)ifnotrequest.is_ajax():# reload the pagenext=request.META['PATH_INFO']returnHttpResponseRedirect(next)# if is_ajax(), we just return the validated form, so the modal will closeelse:form=SomethingForm()returnrender(request,template_name,{'form':form,'object':object,# unused, but armless})where:classSomethingForm(forms.ModelForm):classMeta:model=Someghingexclude=[]...and an endpoint for Ajax call:File “urls.py” …path('j/edit_something/<int:id_object>/',ajax.edit_something,name='j_edit_something'),We can finally use the form in a Dialog:$(document).ready(function(){dialog1=newDialog({dialog_selector:'#dialog_generic',html:'<h1>Loading ...</h1>',url:'/j/edit_something/{{ object.id }}/',width:'400px',min_height:'200px',title:'<i class="fa fa-add"></i> Edit',footer_text:'',enable_trace:true,callback:function(event_name,dialog,params){switch(event_name){case"created":console.log('Dialog created: dialog=%o, params=%o',dialog,params);break;case"submitted":FrontendForms.hide_mouse_cursor();FrontendForms.reload_page(true);break;}}});});1.10Default dialog layoutWhen contructing a Dialog, you can use thedialog_selectoroption to select which
HTML fragment of the page will be treated as the dialog to work with.It is advisable to use an HTML structure similar to the default layout:<divid="dialog_generic"class="dialog draggable"><divclass="dialog-dialog"><divclass="dialog-content"><divclass="dialog-header"><spanclass="spinner"><iclass="fa fa-spinner fa-spin"></i></span><spanclass="close">&times;</span><divclass="title">Title</div></div><divclass="dialog-body ui-front"></div><divclass="dialog-footer"><inputtype="submit"value="Close"class="btn btn-close"/><inputtype="submit"value="Save"class="btn btn-save"/><divclass="text">footer</div></div></div></div></div>Notes:“.draggable” make the Dialog draggableadding “.ui-front” to the “.dialog-box” element helps improving the behaviour of the dialog on a mobile client1.11App SettingsOptionAccepted valuesFRONTEND_FORMS_FORM_LAYOUT_FLAVOR“generic”, “bs4”FRONTEND_FORMS_FORM_LAYOUT_DEFAULT“vertical”, “horizontal”FRONTEND_FORMS_MODEL_FORMS_MODULESDefault values:FRONTEND_FORMS_FORM_LAYOUT_FLAVOR  = "generic"
FRONTEND_FORMS_FORM_LAYOUT_DEFAULT = "vertical"
FRONTEND_FORMS_MODEL_FORMS_MODULES = ['frontend.forms', ]1.12“bs4” flavorAdd the .compact-fields class to the form to modify the layout as in the right picture below:1.13Utilities (module FrontendForms)HelperPurposedisplay_server_error(errorDetails)Display an error message using SweetAlert2; failing that, uses a simple alert insteaddisplay_message(html_content)Display a message using SweetAlert2; failing that, uses a simple alert insteadredirect(url, show_overlay=False)Similar behavior as an HTTP redirect; optionally calls overlay_show(‘body’)gotourl(url, show_overlay=False)Similar behavior as clicking on a link; optionally calls overlay_show(‘body’)reload_page(show_overlay=False)Reload the current page; optionally calls overlay_show(‘body’)overlay_show(element)Show overlay on given element; Requires: gasparesganga-jquery-loading-overlay or {% include ‘frontend_forms/overlay.html’ %}overlay_hide(element)Hide overlay on given element; Requires: gasparesganga-jquery-loading-overlay or {% include ‘frontend_forms/overlay.html’ %}hide_mouse_cursorHide the mouse cursordumpObject(obj, max_depth)Serialize the given dictionary up tomax_depthlevelslogObject(element, obj)Renderobjcontent as HTML table an assign to given elementisEmptyObject(obj)Check if givenobjis emptycloneObject(obj)Deep clone an object in JavaScriptlookup(array, prop, value)Find an Object by attribute in an Arrayformdata_serialize(formData)Serializing form data with the vanilla JS FormData() objectformdata_to_querystring(formData)Transform FormData into query stringadjust_canvas_size(id)Adapts canvas size to desired sizegetCookie(name)Add to POST headers as follows: FrontendForms.getCookie(‘csrftoken’)confirmRemoteAction(url, options, afterDoneCallback)Invoke remote action upon user confirmation.downloadFromAjaxPost(url, params, headers, callback)Handle file download from ajax postquerystring_parse(qs, sep, eq, options)Parse query stringset_datepicker_defaults(language_code)Set datepicker defaults, and optionally select language (“it” or “es” for now)apply_multiselect(elements)Bind MultiSelect widget1.14Form rendering helpersArender_form(form, flavor=None, layout=FORM_LAYOUT_DEFAULT)template tag is available for form rendering:{% load frontend_forms_tags ... %}<formmethod="post">{% csrf_token %}

    {% render_form form %}<divclass="form-group form-submit-row"><buttontype="submit"class="btn btn-lg btn-primary btn-block">{% trans 'Submit' %}</button></div></form>For more a more advanced customization, you can userender_form_field(field, flavor=None, extra_attrs=’’, layout=FORM_LAYOUT_DEFAULT, index=0, addon=’’)instead:{% load frontend_forms_tags ... %}<formmethod="post">{% csrf_token %}

    {% if form.non_field_errors %}<ulclass="errorlist">{% for error in form.non_field_errors %}<li>{{ error }}</li>{% endfor %}</ul>{% endif %}

    {% for hidden_field in form.hidden_fields %}
        {{ hidden_field }}
    {% endfor %}<fieldset>{% render_form_field form.username extra_attrs="autocomplete=^off,role=presentation,autocorrect=off,autocapitalize=none" %}
        {% render_form_field form.password extra_attrs="autocomplete=^off,role=presentation,autocorrect=off,autocapitalize=none" addon='<iclass="fa fa-user"></i>' %}</fieldset><divclass="form-group form-submit-row"><buttontype="submit"class="btn btn-lg btn-primary btn-block">{% trans 'Submit' %}</button></div></form>In this second example, we supplyextra_attrsattributes to each form field; these will be added to the
attributes already derived from the Django Form field definitions.The special prefix^will be removed from the attribute, and interpreted as “replace” instead of “append”.A generic template is also available:generic_form_inner.html:{% load i18n frontend_forms_tags %}<divclass="row"><divclass="col-sm-12"><formaction="{{ action }}"method="post"class="form"novalidateautocomplete="off">{% csrf_token %}
            {% render_form form %}<inputtype="hidden"name="object_id"value="{{ object.id|default:'' }}"><divclass="form-submit-row"><inputtype="submit"value="Save"/></div></form></div></div>Please note that, as a convenience when editing a Django Model, we’ve added an hidden fieldobject_id;
in other occasions, this is useless (but also armless, as long as the form doesn’t
contain a field called “object”).1.15Datepicker supportA basic support is provided for jquery-ui datepicker.Follow these steps:Initialize datepicker default by callingFrontendForms.set_datepicker_defaults(language_code)once:<scriptlanguage="javascript">$(document).ready(function(){moment.locale('it');FrontendForms.set_datepicker_defaults('{{LANGUAGE_CODE}}');<-------------...In your form, make sure that thedatepickerclass is assigned to the input element;
for example:classMyForm(forms.Form):date=forms.DateField(widget=forms.DateInput())...def__init__(self,*args,**kwargs):super().__init__(*args,**kwargs)self.fields['date'].widget=forms.DateInput(attrs={'class':'datepicker'})If loading the form in a dialog, rebind as necessary:dialog1=newDialog({...callback:function(event_name,dialog,params){switch(event_name){case"loaded":bindSelectables();dialog.element.find(".datepicker").datepicker({});<-------------break;...}}});1.16jQuery MultiSelect supportRequirements:<link rel="stylesheet" type="text/css" href="{% static 'multiselect/css/multi-select.css' %}" />

<script src="{% static 'multiselect/js/jquery.multi-select.js' %}"></script>
<script src="{% static 'jquery.quicksearch/dist/jquery.quicksearch.min.js' %}"></script>Follow these steps:In your form, add themultiselectclass to the SelectMultiple() widgetclassMyForm(forms.ModelForm):...def__init__(self,*args,**kwargs):super().__init__(*args,**kwargs)self.fields['operators'].widget.attrs={'class':'multiselect'}Later on, bind the widget usingapply_multiselect()helper:dialog1=newDialog({...callback:function(event_name,dialog,params){switch(event_name){case"loaded":FrontendForms.apply_multiselect(dialog.element.find('.multiselect'));break;...}}});1.17django-select2 supportRequirements:pip install django-select2
npm install select2Changes to “settings.py”:INSTALLED_APPS = [
    ...
    'django_select2',
    ...Changes to “base.html”:<link rel="stylesheet" type="text/css" href="{% static 'select2/dist/css/select2.min.css' %}" />

<script src="{% static 'select2/dist/js/select2.min.js' %}"></script>
<script src="{% static 'select2/dist/js/i18n/it.js' %}"></script>
<script language="javascript">
    $.fn.select2.defaults.set('language', 'it');
</script>
<script src="{% static 'django_select2/django_select2.js' %}"></script>Follow these steps:In your form, use one or more Select2Widget():fromdjango_select2.formsimportHeavySelect2WidgetclassMyForm(forms.ModelForm):...classMeta:...widgets={'fieldname':HeavySelect2Widget(data_url='/url/to/json/response')}Later on, bind the widgets usingdjangoSelect2()helper:dialog1=newDialog({...callback:function(event_name,dialog,params){switch(event_name){case"loaded":dialog.element.find('.django-select2').djangoSelect2({// "dropdownParent" is required for Bootstrap; see:// https://select2.org/troubleshooting/common-problems#select2-does-not-function-properly-when-i-use-it-inside-a-bootstdropdownParent:dialog.element,width:'style'});break;...}}});I normally opt to include all required static files in “base.hmtml”, since I’m already
including so much javascript stuff.In this case, make sure django-select2 won’t istall them twice;
for example:classMySelect2Widget():"""
    Avoid inclusion of select2 by django-select2 as a result of {{form.media}},
    since we're already including everything in base.html
    """def_get_media(self):returnNonemedia=property(_get_media)classAlbumWidget(MySelect2Widget,ModelSelect2Widget):model=Albumsearch_fields=['name__istartswith',]defbuild_attrs(self,base_attrs,extra_attrs=None):attrs=super().build_attrs(base_attrs=base_attrs,extra_attrs=extra_attrs)# "data-minimum-input-length";# - either override build_attrs() here,# - or provide as attr in the instance; for example:#   'album': AlbumWidget(attrs={'data-minimum-input-length': 0,}),attrs['data-minimum-input-length']=0returnattrs2History2.1v0.3.3css cleanup (to keep Whitenoise happy)2.2v0.3.2rewrite utilities for vanilla JS2.3v0.3.1accept JSON response after form validation2.4v0.3.0optionally replace “frontend_form.js” with “frontend_form_vanilla.js”upgrade to Django 4.22.5v0.2.20generic_edit_view() now treats request.FILES2.6v0.2.19[fix] frontend_forms.js was out of sync with frontend_forms.jsx2.7v0.2.18[fix] “it” language selection for datepickerformdata_serialize() and formdata_to_querystring() helpers added2.8v0.2.17diplay_message() helper added2.9v0.2.16small style fix for checkbox in horizontal generic form rendering2.10v0.2.15revised example project2.11v0.2.14Prepare for Django 4.02.12v0.2.13POSSIBLE INCOMPATIBLE CHANGE: Radio button layout refactored in “render_form_field.html”send “submission_failure” notificationpackage up data from form with FormData (instead of form.serialize()) to allow files uploadallow customization ofenctypein generic form templatesend “submission_failure” notification2.13v0.2.12Prevent default on close button submission2.14v0.2.11Replace $() with jQuery() for higher compatibility2.15v0.2.10Add never cache to all views for extra safeness2.16v0.2.9[fix] Properly hide btn_save and btn_close when corresponding label is empty2.17v0.2.8[fix] frontend_forms.js was out of sync with frontend_forms.jsx2.18v0.2.7Removed wrong [fix] render_form_field rendering for bootstrap2.19v0.2.6Update Pillow (example project)2.20v0.2.5Upgrade Django (in example project)[fix] render_form_field rendering for bootstrapOptionally retrieve missing Dialog options from HTML attributessubtitle added to dialog_generic2.21v0.2.4in case of form errors, autofocus now selects the first editable invalid field2.22v0.2.3transpile frontend_forms.jsx2.23v0.2.2non-destructive form_class annotation2.24v0.2.1POSSIBLE INCOMPATIBLE CHANGE: Added javascript catalog for translating messages in JS codeItalian transation addedExample: chain selection sample[fix] Send missing “submitted” notification2.25v0.2.0Login view suitable for modal formscheck_logged_in() decoratorfix format_datetimePOSSIBLE INCOMPATIBLE CHANGE: provided templates now extend “base.html” instead of “frontend/base.html”2.26v0.1.13Improved example project (Creating or updating a Django Model from the front-end)revised confirmRemoteAction() helper2.27v0.1.12Make sure invalid-tooltip is visible with BS42.28v0.1.11Select2 support and examples2.29v0.1.10Small adjustments to default styles; “important” removed where possiblePartial support for Bootstrap’s “input-group-addon”Example updated2.30v0.1.9Giving a feedback after successful form submission2.31v0.1.8Make sure Sweetalert2 pops up above modal dialog2.32v0.1.7render_form_field: show errors for radio groups2.33v0.1.6example django project added2.34v0.1.5autofocus_first_visible_input option added2.35v0.1.4generic Form submission from a Dialog example added to Readmefix horizontal forms for BS4add even/odd class to form groups2.36v0.1.3Display checkbox fields errorsAdjust errors styles2.37v0.1.2Optionally provide therequestto the Form constructorAdd a class attribute ‘form-app_label-model_name’ to the rendered formdjango-select2 supportjQuery MultiSelect support2.38v0.1.1ModalForms module renamed as FrontendFormsoptional parametereventadded to open()2.39v0.1.0Module renamed from “django-modal-forms” to “django-frontend-forms”2.40v0.0.14Fixes for Django 3; support both int and uuid PKs2.41v0.0.13Configurable FRONTEND_FORMS_FORM_LAYOUT_DEFAULT2.42v0.0.12Support for model forms in a Dialog (undocumented)2.43v0.0.11Datepicker support2.44v0.0.10optional extra_attrs added to render_form_field template tag2.45v0.0.9fix confirmRemoteAction()2.46v0.0.8fix2.47v0.0.7add custom widget attrs when rendering a field with render_form_fields()2.48v0.0.6add “has-error” class when appropriate in render_form_field tag, to trigger errors in modal forms2.49v0.0.5“simpletable” fix2.50v0.0.4“simpletable” styles2.51v0.0.3downloadFromAjaxPost helper JS function addedDisplay non_field_errors in BS4 formPrepend fields’ class with ‘field-’ prefix, as Django admin doesRadio buttons and Checkboxs rendering for Bootstrap 4bs4 form renderingquerystring_parse() utility addedAdd object_id hidden field to generic form.ui-front added to .dialog-body for bette behaviour on mobilesnotify “loaded” event in _form_ajax_submit() when approriate2.52v0.0.2First working release2.53v0.0.1Project start