Modal Django Forms Documentation: Mario Orlandi
Modal Django Forms Documentation: Mario Orlandi
Modal Django Forms Documentation: Mario Orlandi
Mario Orlandi
1 Topics 3
1.1 Basic modals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Basic modals with Django . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3 Modals with simple content . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.4 Form validation in the modal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.5 Creating and updating a Django Model in the front-end . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.6 Creating and updating a Django Model in the front-end (optimized) . . . . . . . . . . . . . . . . . . 21
1.7 A fully generic solution for Django models editing in the front-end . . . . . . . . . . . . . . . . . . 23
1.8 References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.9 Possible enhancements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
i
ii
Modal Django Forms Documentation
I try to take advantage of the powerful Django admin in all my web projects, at least in the beginning.
However, as the project evolves and the frontend improves, the usage of the admin tends to be more and more residual.
Adding editing capabilities to the frontend in a modern user interface requires the usage of modal forms, which, to be
honest, have always puzzled me for some reason.
This project is not a reusable Django package, but rather a collection of techniques and examples used to cope with
modal popups, form submission and validation via ajax, and best practices to organize the code in an effective way to
minimize repetitions.
Contents 1
Modal Django Forms Documentation
2 Contents
CHAPTER 1
Topics
$(document).ready(function() {
3
Modal Django Forms Documentation
});
</script>
Note: Check sample code at: (1) A basic modal box with jQuery
How can we collect a value from the user in the modal window, and return it to the main page ?
We have access to any javascript functions available (after all, we’re living in the same HTML page), so we can call
any helper just before closing the modal.
4 Chapter 1. Topics
Modal Django Forms Documentation
function close_popup(modal) {
var value = modal.find('.my-modal-body input').val();
save_text_value(value);
modal.hide();
}
function save_text_value(value) {
if (value) {
$('#result-wrapper').show();
$('#result').text(value);
}
else {
$('#result-wrapper').hide();
}
}
Note: Check sample code at: (2) A basic modal box which returns a value
Always remember to clean the input box every time before showing the modal box, as this will be reused again and
again . . .
function open_popup(modal) {
var input = modal.find('.my-modal-body input');
input.val('');
modal.show();
input.focus();
}
https://getbootstrap.com/docs/3.3/javascript/#modals-events
Note: Check sample code at: (3) A basic modal box with Bootstrap 3
1.2.1 Purpose
Spostando la nostra attenzione su un sito dinamico basato su Django, i nostri obiettivi principali diventano:
• disporre di una dialog box da usare come “contenitore” per l’interazione con l’utente, e il cui layout sia coerente
con la grafica del front-end
• il contenuto della dialog e il ciclo di vita dell’interazione con l’utente viene invece definito e gestito “lato server”
• la dialog viene chiusa una volta che l’utente completato (o annullato) l’operazione
Il layout di ciascuna dialog box (quindi l’intera finestra a meno del contenuto) viene descritto in un template, e il
rendering grafico viene determinato da un unico foglio di stile comune a tutte le finestre (file “modals.css”).
Note: Check sample code at: (4) A generic empty modal for Django” illustra diverse possibilita’
Nel caso piu’ semplice, ci limitiamo a visualizare la dialog prevista dal template:
6 Chapter 1. Topics
Modal Django Forms Documentation
<a href=""
onclick="openModalDialog(event, '#modal_generic'); return false;">
<i class="fa fa-keyboard-o"></i> Open generic modal (no contents, no
˓→customizations)
</a>
Questo e’ sufficiente nei casi in cui il template contenga gia’ tutti gli elementi richiesti; ci sono pero’ buone possibilita’
che un’unica “generica” dialog sia riutilizzabile in diverse circostanze (o forse ovunque) pur di fornire un minimo di
informazioni accessorie:
<a href=""
data-dialog-class="modal-lg"
data-title="Set value"
data-subtitle="Insert the new value to be assigned to the Register"
data-icon="fa-keyboard-o"
data-button-save-label="Save"
onclick="openModalDialog(event, '#modal_generic'); return false;">
<i class="fa fa-keyboard-o"></i> Open generic modal (no contents)
</a>
In entrambi i casi si fa’ riperimento a un semplice javascript helper, che provvede ad aggiornare gli attributi della dialog
prima di visualizzarla, dopo avere reperito i dettagli dall’elemento che l’ha invocata; il vantaggio di questo approccio
e’ che possiamo definire questi dettagli nel template della pagina principale, e quindi utilizzandone il contesto:
<script language="javascript">
Sample call:
<a href=""
data-title="Set value"
data-subtitle="Insert the new value to be assigned to the Register"
data-dialog-class="modal-lg"
data-icon="fa-keyboard-o"
data-button-save-label="Save"
onclick="openModalDialog(event, '#modal_generic'); return false;">
<i class="fa fa-keyboard-o"></i> Open generic modal (no contents)
</a>
*/
var modal = $(element);
var target = $(event.target);
modal.find('.modal-dialog').attr('class', dialog_class);
modal.find('.modal-title').text(title);
modal.find('.modal-subtitle').text(subtitle);
modal.find('.modal-header .title-wrapper i').attr('class', icon_class);
(continues on next page)
8 Chapter 1. Topics
Modal Django Forms Documentation
return modal;
}
</script>
To have the modal draggable, you can specify the “draggable” class:
<div class="modal-dialog">
...
if (modal.hasClass('draggable')) {
modal.find('.modal-dialog').draggable({
handle: '.modal-header'
});
}
.modal.draggable .modal-header {
cursor: move;
}
Per convenienza, tutti i templates relativi alle dialog (quello generico e le eventuali varianti specializzate) vengono
memorizzate in un unico folder:
templates/frontent/modals
e automaticamente incluse nel template “base.html”:
{% block modals %}
{% include 'frontend/modals/generic.html' %}
{% include 'frontend/modals/dialog1.html' %}
{% include 'frontend/modals/dialog2.html' %}
(continues on next page)
Questo significa che tutte le modal dialogs saranno disponibili in qualunque pagina, anche quando non richieste;
trattandosi di elementi non visibili della pagina, non ci sono particolari controindicazioni; nel caso, il template specifico
puo’ eventulmente ridefinire il blocco {% block modals %} ed includere i soli template effettivamente necessari.
Altri files utilizzati:
• static/frontend/css/modals.css: stili comuni a tutte le dialogs
• static/frontend/js/modals.js: javascript helpers pertinenti alla gestione delle dialogs
Possiamo riempire il contenuto della dialog invocando via Ajax una vista che restituisca l’opportuno frammento
HTML:
<script language="javascript">
function openMyModal(event) {
var modal = initModalDialog(event, '#modal_generic');
var url = $(event.target).data('action');
modal.find('.modal-body').load(url, function() {
modal.modal('show');
});
}
</script>
def simple_content(request):
return HttpResponse('Lorem ipsum dolor sit amet, consectetur adipiscing elit.
˓→Proin dignissim dapibus ipsum id elementum. Morbi in justo purus. Duis ornare
˓→lobortis nisl eget condimentum. Donec quis lorem nec sapien vehicula eleifend vel
Si osservi come abbiamo specificato l’url della view remota nell’attributo “data-action” del trigger.
Un limite di questa semplice soluzione e’ che non siamo in grado di rilevare eventuali errori del server, e quindi in
caso di errore la dialog verrebbe comunque aperta (con contenuto vuoto).
Il problema viene facilmente superato invocando direttamente $.ajax() anziche’ lo shortcut load().
La soluzione e’ leggermente piu’ verbose, ma consente un controllo piu’ accurrato:
<script language="javascript">
function openMyModal(event) {
var modal = initModalDialog(event, '#modal_generic');
var url = $(event.target).data('action');
$.ajax({
type: "GET",
url: url
}).done(function(data, textStatus, jqXHR) {
modal.find('.modal-body').html(data);
modal.modal('show');
(continues on next page)
10 Chapter 1. Topics
Modal Django Forms Documentation
</script>
def simple_content_forbidden(request):
raise PermissionDenied
A volte puo’ essere utile riutilizzare la stessa view per fornire, a seconda delle circostanze, una dialog modale oppure
una pagina standalone.
def simple_content2(request):
<div class="row">
<div class="col-sm-4">
{% lorem 1 p random %}
</div>
<div class="col-sm-4">
{% lorem 1 p random %}
</div>
<div class="col-sm-4">
{% lorem 1 p random %}
</div>
</div>
mentre il template “esterno” si limita a includerlo nel contesto piu’ completo previsto dal frontend:
{% extends "base.html" %}
{% load static staticfiles i18n %}
{% block content %}
{% include 'frontend/includes/simple_content2_inner.html' %}
{% endblock content %}
Note: Check sample code at: (5) Modal with simple content
We’ve successfully injected data retrieved from the server in our modals, but did not really interact with the user yet.
When the modal body contains a form, things start to become interesting and tricky.
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:
12 Chapter 1. Topics
Modal Django Forms Documentation
• 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
We’ll obtain all this (and more) just calling a single helper function formAjaxSubmit() which I’ll explain in detail
later.
<script language="javascript">
function openMyModal(event) {
var modal = initModalDialog(event, '#modal_generic');
var url = $(event.target).attr('href');
$.ajax({
type: "GET",
url: url
}).done(function(data, textStatus, jqXHR) {
modal.find('.modal-body').html(data);
modal.modal('show');
formAjaxSubmit(modal, url, null, null);
}).fail(function(jqXHR, textStatus, errorThrown) {
alert("SERVER ERROR: " + errorThrown);
});
}
</script>
Again, the very same view can also be used to render a standalone page:
14 Chapter 1. Topics
Modal Django Forms Documentation
Fig. 6: When form did not validate, we keep the dialog open
setTimeout(function() {
modal.find('form input:visible').first().focus();
}, 1000);
16 Chapter 1. Topics
Modal Django Forms Documentation
</script>
Note tha if the form specifies an action, we use it as end-point of the ajax call; if not, we’re using the same view for
both rendering and form processing, so we can reuse the original url instead:
Secondly, we need to detect any form errors after submission; see the “success” callback after the Ajax call for details.
We also need to cope with the submit button embedded in the form.
While it’s useful and necessary for the rendering of a standalone page, it’s rather disturbing in the modal dialog:
Here’s the relevant code:
During content loading, we add a “loading” class to the dialog header, making a spinner icon visible.
Fig. 7: Can we hide the “Send” button and use the “Save” button from the footer instead ?
• cbAfterLoad: called every time new content has been loaded; you can use it to bind more form controls
• cbAfterSuccess: called after successfull submission; at this point the modal has been closed, but the bounded
form might still contain useful informations that you can grab for later inspection
Sample usage:
...
formAjaxSubmit(modal, url, afterModalLoad, afterModalSuccess);
...
function afterModalLoad(modal) {
console.log('modal %o loaded', modal);
}
function afterModalSuccess(modal) {
console.log('modal %o succeeded', modal);
}
Note: Check sample code at: (6) Form validation in the modal
Warning: In the sample project, a sleep of 1 sec has been included in the view (POST) to simulate a more
complex elaboration which might occur in real situations
18 Chapter 1. Topics
Modal Django Forms Documentation
We can now apply what we’ve built so far to edit a specific Django model from the front-end.
Note: Check sample code at: (7) Creating and updating a Django Model in the front-end‘ in the sample project
@login_required
def artist_create(request):
if not request.user.has_perm('backend.add_artist'):
raise PermissionDenied
object = None
if request.method == 'POST':
form = ArtistCreateForm(data=request.POST)
if form.is_valid():
object = form.save()
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 = ArtistCreateForm()
class ArtistCreateForm(forms.ModelForm):
class Meta:
model = Artist
fields = [
'description',
'notes',
]
On successful creation, we might want to update the user interface; in the example, for simplicity, we just reload the
entire page, but also display the new object id retrieved from the hidden field ‘object_id’ of the form; this could be
conveniently used for in-place page updating.
<script language="javascript">
function afterModalCreateSuccess(modal) {
var object_id = modal.find('input[name=object_id]').val();
alert('New artist created: id=' + object_id);
location.reload(true);
}
</script>
@login_required
def artist_update(request, pk):
if not request.user.has_perm('backend.change_artist'):
raise PermissionDenied
class ArtistUpdateForm(forms.ModelForm):
class Meta:
model = Artist
(continues on next page)
20 Chapter 1. Topics
Modal Django Forms Documentation
function afterModalUpdateSuccess(modal) {
var object_id = modal.find('input[name=object_id]').val();
alert('Artist updated: id=' + object_id);
location.reload(true);
}
</script>
Note: Check sample code at: (8) Editing a Django Model in the front-end, using a common basecode for creation
and updating
Sharing a single view for both creating a new specific Model and updating an existing one is now straitforward; see
artist_edit() belows:
################################################################################
# A single "edit" view to either create a new Artist or update an existing one
# Retrieve object
if pk is None:
# "Add" mode
object = None
required_permission = 'backend.add_artist'
else:
(continues on next page)
raise PermissionDenied
if request.method == 'POST':
form = ArtistUpdateForm(instance=object, data=request.POST)
if form.is_valid():
object = form.save()
if not request.is_ajax():
# reload the page
if pk is None:
message = 'The object "%s" was added successfully.' % object
else:
message = 'The object "%s" was changed successfully.' % object
messages.success(request, message)
next = request.META['PATH_INFO']
return HttpResponseRedirect(next)
# if is_ajax(), we just return the validated form, so the modal will close
else:
form = ArtistUpdateForm(instance=object)
When “pk” is None, we switch to “add” mode, otherwise we retrieve the corresponding object to change it.
The urls are organized as follows:
urlpatterns = [
...
path('artist/add/', views.artist_edit, {'pk': None, }, name="artist-add"),
path('artist/<uuid:pk>/change/', views.artist_edit, name="artist-change"),
...
]
class Meta:
model = Artist
(continues on next page)
22 Chapter 1. Topics
Modal Django Forms Documentation
The javascript handler which opens the dialog can be refactored in a completely generic way, with no reference to the
specific Model in use:
<script language="javascript">
</script>
so I moved it from the template to “modals.js”. It can be invoked directly from there, or copied to any local template
for further customization.
1.7 A fully generic solution for Django models editing in the front-end
We’re really very close to the Holy Grail of Django models editing in the front-end.
Can we really do it all with a single generic view ?
Yes sir !
Note: Check sample code at: (9) A fully generic solution for Django models editing in the front-end
################################################################################
# A fully generic "edit" view to either create a new object or update an existing one;
# works with any Django model
model_class = model_form_class._meta.model
app_label = model_class._meta.app_label
model_name = model_class._meta.model_name
model_verbose_name = model_class._meta.verbose_name.capitalize()
# Retrieve object
if pk is None:
(continues on next page)
1.7. A fully generic solution for Django models editing in the front-end 23
Modal Django Forms Documentation
raise PermissionDenied
if request.method == 'POST':
form = model_form_class(instance=object, data=request.POST)
if form.is_valid():
object = form.save()
if not request.is_ajax():
# reload the page
if pk is None:
message = 'The %s "%s" was added successfully.' % (model_verbose_
˓→name, object)
else:
message = 'The %s "%s" was changed successfully.' % (model_
˓→verbose_name, object)
messages.success(request, message)
next = request.META['PATH_INFO']
return HttpResponseRedirect(next)
# if is_ajax(), we just return the validated form, so the modal will close
else:
form = model_form_class(instance=object)
Adding a ModelForm specification at run-time is all what we need; from it, we deduct the Model that’s it.
Just remember to supply a ModelForm in the urls and you’ve done:
urlpatterns = [
...
path('album/<uuid:pk>/change/', views.generic_edit_view, {'model_form_class':
˓→forms.AlbumEditForm}, name="album-change"),
...
]
24 Chapter 1. Topics
Modal Django Forms Documentation
...
1.8 References
1.8. References 25
Modal Django Forms Documentation
Django admin offers a rich environment for data editing; we might evolve the project to provide similar functionalities:
Fieldsets Check this for inspiration: https://schinckel.net/2013/06/14/django-fieldsets/
Filtered lookup of related models As ModelAdmin does with formfield_for_foreignkey and form-
field_for_manytomany
Support for raw_id_fields Check https://github.com/lincolnloop/django-dynamic-raw-id/ and https://
www.abidibo.net/blog/2015/02/06/pretty-raw_id_fields-django-salmonella-and-django-grappelli/
Minor issues:
• Add a localized datepicker in the examples
• Give an example for object deletion (upon confirmation)
• Add a ModelMultipleChoiceField and multiSelect example in the sample project
• Accept and optional “next” parameter for redirection after successfull form submission (for standalone pages)
26 Chapter 1. Topics
CHAPTER 2
• genindex
• modindex
• search
27