django-frontend-forms 0.3.3

Creator: codyrutscher

Last updated:

Add to Cart

Description:

djangofrontendforms 0.3.3

1 django-frontend-forms
A Django helper app to add editing capabilities to the frontend using modal forms.
An accompaning Demo site
provides:

usage instructions and suggestions
a detailed description of the techniques used under the hood
a 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 end


Contents

1 django-frontend-forms

1.1 Installation
1.2 How to use it

1.2.1 Basic example
1.2.2 Dialog methods
1.2.3 Dialog options
1.2.4 Dialog notifications


1.3 Handling form submission
1.4 Giving a feedback after successful form submission
1.5 Returning JSON result after form validation
1.6 Logging in with a modal form
1.7 Replacing login_required
1.8 A full, real example for a Django Form submission from a Dialog
1.9 Editing a Django Model from a Dialog
1.10 Default dialog layout
1.11 App Settings
1.12 “bs4” flavor
1.13 Utilities (module FrontendForms)
1.14 Form rendering helpers
1.15 Datepicker support
1.16 jQuery MultiSelect support
1.17 django-select2 support


2 History

2.1 v0.3.3
2.2 v0.3.2
2.3 v0.3.1
2.4 v0.3.0
2.5 v0.2.20
2.6 v0.2.19
2.7 v0.2.18
2.8 v0.2.17
2.9 v0.2.16
2.10 v0.2.15
2.11 v0.2.14
2.12 v0.2.13
2.13 v0.2.12
2.14 v0.2.11
2.15 v0.2.10
2.16 v0.2.9
2.17 v0.2.8
2.18 v0.2.7
2.19 v0.2.6
2.20 v0.2.5
2.21 v0.2.4
2.22 v0.2.3
2.23 v0.2.2
2.24 v0.2.1
2.25 v0.2.0
2.26 v0.1.13
2.27 v0.1.12
2.28 v0.1.11
2.29 v0.1.10
2.30 v0.1.9
2.31 v0.1.8
2.32 v0.1.7
2.33 v0.1.6
2.34 v0.1.5
2.35 v0.1.4
2.36 v0.1.3
2.37 v0.1.2
2.38 v0.1.1
2.39 v0.1.0
2.40 v0.0.14
2.41 v0.0.13
2.42 v0.0.12
2.43 v0.0.11
2.44 v0.0.10
2.45 v0.0.9
2.46 v0.0.8
2.47 v0.0.7
2.48 v0.0.6
2.49 v0.0.5
2.50 v0.0.4
2.51 v0.0.3
2.52 v0.0.2
2.53 v0.0.1





1.1 Installation
Install the package by running:
pip install django-frontend-forms

or:

pip install git+https://github.com/morlandi/django-frontend-forms
In your settings, add:
INSTALLED_APPS = [
...
'frontend_forms',
]
Include library’s views mapping (file urls.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:
<link rel='stylesheet' href="{% static 'frontend_forms/css/frontend_forms.css' %}">
{% if USE_VANILLA_JS %}
<script src="{% static 'frontend_forms/js/frontend_forms_vanilla.js' %}"></script>
{% else %}
<script src="{% static 'frontend_forms/js/frontend_forms.js' %}"></script>
{% endif %}
<script src="{% url 'frontend_forms:javascript-catalog' %}"></script>
{% include 'frontend_forms/dialogs.html' %}


1.2 How to use it
Follow the intructions given on the Demo site

1.2.1 Basic example
In 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.
<script language="javascript">

$(document).ready(function() {

dialog1 = new Dialog({
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>


<a href="#" class="btn btn-primary pull-right" onclick="dialog1.open(event); return false;">
<i class="fa fa-plus-circle"></i>
Test Popup
</a>


1.2.2 Dialog methods


Method
Effects

constructor(options={})
See options list below

open(event=null, show=true)
Open the dialog

the 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 loading



close()
Close (hide) the dialog

show()
Make the dialog visible





1.2.3 Dialog options


Option
Default value
Notes

dialog_selector
‘#dialog_generic’
The selector for HTML dialog template

open_event
null
Used to “remember” the event which triggered Dialog opening

html
‘’
Static content to display in dialog body

url
‘’
Optional url to retrieve dialog content via Ajax

width
null


min_width
null


max_width
null


height
null


min_height
null


max_height
null


button_save_label
‘Save’


button_save_initially_hidden
false
Will be shown after form rendering

button_close_label
‘Cancel’


title
‘’


subtitle
‘’


footer_text
‘’


enable_trace
false
show notifications in debug console

callback
null
a callback to receive events

autofocus_first_visible_input
true




Unspecified options will be retrieved from corresponding HTML attributes on the
element which fires the dialog opening;
for example:
<a href="{% url 'frontend:whatever' object.id %}"
data-title="My title"
data-subtitle="My Subtitle"
onclick="new Dialog().open(event); return false;">
Open
</a>


Option
HTML attribute

url
href

html
data-html

width
data-width

min_width
data-min-width

max_width
data-max-width

height
data-height

min_height
data-min-height

max_height
data-max-height

button_save_label
data-button-save-label

button_close_label
data-button-close-label

title
data-title

subtitle
data-subtitle

footer_text
data-footer-text





1.2.4 Dialog notifications


event_name
params



created
options

closed


initialized


shown


loading
url

loaded
url, data

loading_failed
jqXHR, textStatus, errorThrown

open


submitting
method, url, data

submission_failure
method, url, data

submitted
method, url, data



During 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 = new Dialog({
...
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 flag enable_trace.



1.3 Handling form submission
When a form submission is involved, the modal life cycle has to be modified as follows:

First and foremost, we need to prevent 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 errors
otherwise, user interaction is completed, and we can finally close the modal




django-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'),
...
]


from django import forms

class UserProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = [
'whatever',
...
]


from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import login_required
from django.views.decorators.cache import never_cache
from django.shortcuts import render

@login_required
@never_cache
def edit_profile(request):

is_ajax_request = request.accepts("application/json")
if not is_ajax_request:
raise PermissionDenied

template_name = 'frontend_forms/generic_form_inner.html'
if request.method == 'POST':
form = UserProfileForm(data=request.POST, instance=request.user.profile)
if form.is_valid():
form.save()
else:
form = UserProfileForm(instance=request.user.profile)

return render(request, template_name, {
'form': form,
'object': request.user, # unused, but armless
})
and later on:
<a href="{% 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:
<a class="btn btn-info" href="#" onclick="dialog_edit_profile.open(); return false;">
<i class="fa fa-user"></i> {% trans 'Edit Profile ...' %}
</a>

<script language="javascript">

$(document).ready(function() {

dialog_edit_profile = new Dialog({
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-bootst
dropdownParent: dialog.element,
width: 'style'
});
break;
case "submitted":
FrontendForms.hide_mouse_cursor();
FrontendForms.reload_page(true);
break;
}
}
});

});
</script>


1.4 Giving a feedback after successful form submission
Sometimes, 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 close
the “save” button will be hidden

thus giving to the user a chance to read your feedback.
def form_validation_with_feedback(request):

assert request.is_ajax()

if request.method == 'POST':
form = MyForm(data=request.POST)
if form.is_valid():
form.save()
return HttpResponse("<h1>Great !</h1> Your form has been validated")
else:
form = MyForm()

return render(request, "my_form.html", {
'form': form,
})


1.5 Returning JSON result after form validation
...
if request.method == 'POST':
form = MyForm(data=request.POST)
if form.is_valid():
form.save()
if not is_ajax(request):
messages.info(request, "Form has been validated")
else:
return JsonResponse(form.cleaned_data)


1.6 Logging in with a modal form
If 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:
<a id="login_with_dialog" href="{% url 'frontend_forms:login' %}">
<i class="fa fa-sign-in"></i>
Login
</a>

<script language="javascript">

$(document).ready(function() {

$('#login_with_dialog').on('click', function(event) {
event.preventDefault();
var target = $(event.target);
var url = target.attr('href');
var logged_in = false;

var login_dialog = new Dialog({
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.html
frontend_forms/login_inner.html
frontend_forms/login_successful_message.html



1.7 Replacing login_required
A decorator suitable for modal forms is provided to replace login_required():
from frontend_forms.decorators import check_logged_in

@check_logged_in()
def my_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.html



1.8 A full, real example for a Django Form submission from a Dialog

We start by creating a view for form rendering and submission:
file ajax.py:
import time
from frontend_forms.decorators import check_logged_in
from django.views.decorators.cache import never_cache
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect


@check_logged_in()
@never_cache
def select_contract(request):

# if settings.DEBUG:
# time.sleep(0.5);

if not request.user.has_perm('backend.view_contract') or not request.is_ajax():
raise PermissionDenied

#template_name = 'frontend/dialogs/generic_form_inner_with_video.html'
template_name = 'dashboard/dialogs/select_contract.html'

object = None
if request.method == 'POST':
form = SelectContractForm(request=request, data=request.POST)
if form.is_valid():
object = form.save(request)
if not request.is_ajax():
# reload the page
next = request.META['PATH_INFO']
return HttpResponseRedirect(next)
# if is_ajax(), we just return the validated form, so the modal will close
else:
form = SelectContractForm(request=request)

return render(request, template_name, {
'form': form,
'object': object, # unused, but armless
})
and provide an endpoint to it for ajax call:
file urls.py
from django.urls import path
from . import ajax

app_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 class
receives the request upon construction
uses it to provide specific initial values to the widgets
provides some specific validations with clean()
encapsulates in save() all actions required after successfull submission

file forms.py:
import json
import datetime
from django import forms
from selectable.forms import AutoCompleteWidget, AutoCompleteSelectWidget, AutoComboboxSelectWidget
from backend.models import Contract
from django.utils.safestring import mark_safe
from .lookups import ContractLookup


class SelectContractForm(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)

class Media:
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'})
assert request.user.is_authenticated and request.user.is_active
self.fields['contract'].initial = request.user.contract_attivo
self.fields['date'].initial = request.user.data_attiva
self.fields['today'].initial = request.user.data_attiva is None

def lookup_contract(self):
try:
contract = Contract.objects.get(
id=self.cleaned_data['contract']
)
except Contract.DoesNotExist:
contract = None
return contract

def clean(self):
cleaned_data = self.cleaned_data
if not cleaned_data['today'] and not cleaned_data['date']:
raise forms.ValidationError({
'date': 'Questo campo è obbligatorio'
})
return cleaned_data

def save(self, request):
user = request.user
assert request.user.is_authenticated and request.user.is_active
user.contract_attivo = self.lookup_contract()
if self.cleaned_data['today']:
user.data_attiva = None
else:
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:
function onChangeToday(event) {
var controller = $('#id_today');
var value = 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 }}

<div class="row">
<div class="col-sm-12">
<form action="{{ action }}" method="post" class="form {{form.form_class}}" novalidate autocomplete="off">
{% csrf_token %}

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

{% if form.non_field_errors %}
<ul class="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>
<div class="data-selection-block">
{% render_form_field form.today %}
{% render_form_field form.date %}
</div>
</fieldset>

<input type="hidden" name="object_id" value="{{ object.id|default:'' }}">
<div class="form-submit-row">
<input type="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 the loaded event notification to rebind the widgets
after form rendering.
{% block extrajs %}
<script language="javascript">
$(document).ready(function() {

dialog1 = new Dialog({
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.9 Editing a Django Model from a Dialog
TODO: TO BE REFINED … AND VERIFIED ;)
First of all, we need a view for form rendering and submission.
For example:
@check_logged_in()
@never_cache
def edit_something(request, id_object=None):

# if not request.user.has_perm('backend.view_something') or not request.is_ajax():
# raise PermissionDenied

if id_object is not None:
object = get_object_or_404(Something, id=id_object)
else:
object = None

template_name = 'frontend_forms/generic_form_inner.html'

if request.method == 'POST':

form = SomethingForm(data=request.POST, instance=object)
if form.is_valid():
object = form.save(request)
if not request.is_ajax():
# reload the page
next = request.META['PATH_INFO']
return HttpResponseRedirect(next)
# if is_ajax(), we just return the validated form, so the modal will close
else:
form = SomethingForm()

return render(request, template_name, {
'form': form,
'object': object, # unused, but armless
})
where:
class SomethingForm(forms.ModelForm):

class Meta:
model = Someghing
exclude = []

...
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 = new Dialog({
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.10 Default dialog layout
When contructing a Dialog, you can use the dialog_selector option 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:
<div id="dialog_generic" class="dialog draggable">
<div class="dialog-dialog">
<div class="dialog-content">
<div class="dialog-header">
<span class="spinner">
<i class="fa fa-spinner fa-spin"></i>
</span>
<span class="close">&times;</span>
<div class="title">Title</div>
</div>
<div class="dialog-body ui-front">

</div>
<div class="dialog-footer">
<input type="submit" value="Close" class="btn btn-close" />
<input type="submit" value="Save" class="btn btn-save" />
<div class="text">footer</div>
</div>
</div>
</div>
</div>
Notes:

“.draggable” make the Dialog draggable
adding “.ui-front” to the “.dialog-box” element helps improving the behaviour of the dialog on a mobile client



1.11 App Settings


Option
Accepted values

FRONTEND_FORMS_FORM_LAYOUT_FLAVOR
“generic”, “bs4”

FRONTEND_FORMS_FORM_LAYOUT_DEFAULT
“vertical”, “horizontal”

FRONTEND_FORMS_MODEL_FORMS_MODULES




Default values:
FRONTEND_FORMS_FORM_LAYOUT_FLAVOR = "generic"
FRONTEND_FORMS_FORM_LAYOUT_DEFAULT = "vertical"
FRONTEND_FORMS_MODEL_FORMS_MODULES = ['frontend.forms', ]


1.12 “bs4” flavor
Add the .compact-fields class to the form to modify the layout as in the right picture below:



1.13 Utilities (module FrontendForms)


Helper
Purpose

display_server_error(errorDetails)
Display an error message using SweetAlert2; failing that, uses a simple alert instead

display_message(html_content)
Display a message using SweetAlert2; failing that, uses a simple alert instead

redirect(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_cursor
Hide the mouse cursor

dumpObject(obj, max_depth)
Serialize the given dictionary up to max_depth levels

logObject(element, obj)
Render obj content as HTML table an assign to given element

isEmptyObject(obj)
Check if given obj is empty

cloneObject(obj)
Deep clone an object in JavaScript

lookup(array, prop, value)
Find an Object by attribute in an Array

formdata_serialize(formData)
Serializing form data with the vanilla JS FormData() object

formdata_to_querystring(formData)
Transform FormData into query string

adjust_canvas_size(id)
Adapts canvas size to desired size

getCookie(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 post

querystring_parse(qs, sep, eq, options)
Parse query string

set_datepicker_defaults(language_code)
Set datepicker defaults, and optionally select language (“it” or “es” for now)

apply_multiselect(elements)
Bind MultiSelect widget





1.14 Form rendering helpers
A render_form(form, flavor=None, layout=FORM_LAYOUT_DEFAULT) template tag is available for form rendering:
{% load frontend_forms_tags ... %}

<form method="post">
{% csrf_token %}

{% render_form form %}

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

<form method="post">
{% csrf_token %}

{% if form.non_field_errors %}
<ul class="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='<i class="fa fa-user"></i>' %}
</fieldset>

<div class="form-group form-submit-row">
<button type="submit" class="btn btn-lg btn-primary btn-block">{% trans 'Submit' %}</button>
</div>
</form>
In this second example, we supply extra_attrs attributes 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 %}

<div class="row">
<div class="col-sm-12">
<form action="{{ action }}" method="post" class="form" novalidate autocomplete="off">
{% csrf_token %}
{% render_form form %}
<input type="hidden" name="object_id" value="{{ object.id|default:'' }}">
<div class="form-submit-row">
<input type="submit" value="Save" />
</div>
</form>
</div>
</div>
Please note that, as a convenience when editing a Django Model, we’ve added an hidden field object_id;
in other occasions, this is useless (but also armless, as long as the form doesn’t
contain a field called “object”).


1.15 Datepicker support
A basic support is provided for jquery-ui datepicker.
Follow these steps:

Initialize datepicker default by calling FrontendForms.set_datepicker_defaults(language_code) once:

<script language="javascript">
$(document).ready(function() {
moment.locale('it');

FrontendForms.set_datepicker_defaults('{{LANGUAGE_CODE}}'); <-------------
...

In your form, make sure that the datepicker class is assigned to the input element;
for example:

class MyForm(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 = new Dialog({
...
callback: function(event_name, dialog, params) {
switch (event_name) {
case "loaded":
bindSelectables();
dialog.element.find(".datepicker").datepicker({}); <-------------
break;
...
}
}
});


1.16 jQuery MultiSelect support
Requirements:
<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 the multiselect class to the SelectMultiple() widget

class MyForm(forms.ModelForm):

...

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['operators'].widget.attrs = {'class': 'multiselect'}

Later on, bind the widget using apply_multiselect() helper:

dialog1 = new Dialog({
...
callback: function(event_name, dialog, params) {
switch (event_name) {
case "loaded":
FrontendForms.apply_multiselect(dialog.element.find('.multiselect'));
break;
...
}
}
});


1.17 django-select2 support
Requirements:
pip install django-select2
npm install select2
Changes 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():

from django_select2.forms import HeavySelect2Widget

class MyForm(forms.ModelForm):

...

class Meta:
...
widgets = {
'fieldname': HeavySelect2Widget(
data_url='/url/to/json/response'
)
}

Later on, bind the widgets using djangoSelect2() helper:

dialog1 = new Dialog({
...
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-bootst
dropdownParent: 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:
class MySelect2Widget():
"""
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):
return None
media = property(_get_media)


class AlbumWidget(MySelect2Widget, ModelSelect2Widget):
model = Album
search_fields = [
'name__istartswith',
]

def build_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'] = 0
return attrs



2 History

2.1 v0.3.3

css cleanup (to keep Whitenoise happy)



2.2 v0.3.2

rewrite utilities for vanilla JS



2.3 v0.3.1

accept JSON response after form validation



2.4 v0.3.0

optionally replace “frontend_form.js” with “frontend_form_vanilla.js”
upgrade to Django 4.2



2.5 v0.2.20

generic_edit_view() now treats request.FILES



2.6 v0.2.19

[fix] frontend_forms.js was out of sync with frontend_forms.jsx



2.7 v0.2.18

[fix] “it” language selection for datepicker
formdata_serialize() and formdata_to_querystring() helpers added



2.8 v0.2.17

diplay_message() helper added



2.9 v0.2.16

small style fix for checkbox in horizontal generic form rendering



2.10 v0.2.15

revised example project



2.11 v0.2.14

Prepare for Django 4.0



2.12 v0.2.13

POSSIBLE INCOMPATIBLE CHANGE: Radio button layout refactored in “render_form_field.html”
send “submission_failure” notification
package up data from form with FormData (instead of form.serialize()) to allow files upload
allow customization of enctype in generic form template
send “submission_failure” notification



2.13 v0.2.12

Prevent default on close button submission



2.14 v0.2.11

Replace $() with jQuery() for higher compatibility



2.15 v0.2.10

Add never cache to all views for extra safeness



2.16 v0.2.9

[fix] Properly hide btn_save and btn_close when corresponding label is empty



2.17 v0.2.8

[fix] frontend_forms.js was out of sync with frontend_forms.jsx



2.18 v0.2.7

Removed wrong [fix] render_form_field rendering for bootstrap



2.19 v0.2.6

Update Pillow (example project)



2.20 v0.2.5

Upgrade Django (in example project)
[fix] render_form_field rendering for bootstrap
Optionally retrieve missing Dialog options from HTML attributes
subtitle added to dialog_generic



2.21 v0.2.4

in case of form errors, autofocus now selects the first editable invalid field



2.22 v0.2.3

transpile frontend_forms.jsx



2.23 v0.2.2

non-destructive form_class annotation



2.24 v0.2.1

POSSIBLE INCOMPATIBLE CHANGE: Added javascript catalog for translating messages in JS code
Italian transation added
Example: chain selection sample
[fix] Send missing “submitted” notification



2.25 v0.2.0

Login view suitable for modal forms
check_logged_in() decorator
fix format_datetime
POSSIBLE INCOMPATIBLE CHANGE: provided templates now extend “base.html” instead of “frontend/base.html”



2.26 v0.1.13

Improved example project (Creating or updating a Django Model from the front-end)
revised confirmRemoteAction() helper



2.27 v0.1.12

Make sure invalid-tooltip is visible with BS4



2.28 v0.1.11

Select2 support and examples



2.29 v0.1.10

Small adjustments to default styles; “important” removed where possible
Partial support for Bootstrap’s “input-group-addon”
Example updated



2.30 v0.1.9

Giving a feedback after successful form submission



2.31 v0.1.8

Make sure Sweetalert2 pops up above modal dialog



2.32 v0.1.7

render_form_field: show errors for radio groups



2.33 v0.1.6

example django project added



2.34 v0.1.5

autofocus_first_visible_input option added



2.35 v0.1.4

generic Form submission from a Dialog example added to Readme
fix horizontal forms for BS4
add even/odd class to form groups



2.36 v0.1.3

Display checkbox fields errors
Adjust errors styles



2.37 v0.1.2

Optionally provide the request to the Form constructor
Add a class attribute ‘form-app_label-model_name’ to the rendered form
django-select2 support
jQuery MultiSelect support



2.38 v0.1.1

ModalForms module renamed as FrontendForms
optional parameter event added to open()



2.39 v0.1.0

Module renamed from “django-modal-forms” to “django-frontend-forms”



2.40 v0.0.14

Fixes for Django 3; support both int and uuid PKs



2.41 v0.0.13

Configurable FRONTEND_FORMS_FORM_LAYOUT_DEFAULT



2.42 v0.0.12

Support for model forms in a Dialog (undocumented)



2.43 v0.0.11

Datepicker support



2.44 v0.0.10

optional extra_attrs added to render_form_field template tag



2.45 v0.0.9

fix confirmRemoteAction()



2.46 v0.0.8

fix



2.47 v0.0.7

add custom widget attrs when rendering a field with render_form_fields()



2.48 v0.0.6

add “has-error” class when appropriate in render_form_field tag, to trigger errors in modal forms



2.49 v0.0.5

“simpletable” fix



2.50 v0.0.4

“simpletable” styles



2.51 v0.0.3

downloadFromAjaxPost helper JS function added
Display non_field_errors in BS4 form
Prepend fields’ class with ‘field-’ prefix, as Django admin does
Radio buttons and Checkboxs rendering for Bootstrap 4
bs4 form rendering
querystring_parse() utility added
Add object_id hidden field to generic form
.ui-front added to .dialog-body for bette behaviour on mobiles
notify “loaded” event in _form_ajax_submit() when approriate



2.52 v0.0.2

First working release



2.53 v0.0.1

Project start

License

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Customer Reviews

There are no reviews.