A Django plugin for creating AJAX driven forms in Bootstrap modal.
This repository includes Dockerfile and docker-compose.yml files so you can easily setup and start to experiment with django-bootstrap-modal-forms running inside of a container on your local machine. Any changes you make in bootstrap_modal_forms, examples and test folders are reflected in the container (see docker-compose.yml) and the data stored in sqlite3 database are persistent even if you remove stopped container. Follow the steps below to run the app:
$ clone repository $ cd django-bootstrap-modal-forms $ docker compose up (use -d flag to run app in detached mode in the background) $ visit 0.0.0.0:8000
Install
django-bootstrap-modal-forms:$ pip install django-bootstrap-modal-forms
Add
bootstrap_modal_formsto your INSTALLED_APPS in settings.py:INSTALLED_APPS = [ ... 'bootstrap_modal_forms', ... ]
Include Bootstrap, jQuery and
jquery.bootstrap.modal.forms.json every page where you would like to set up the AJAX driven Django forms in Bootstrap modal.
IMPORTANT: Adjust Bootstrap and jQuery file paths to match yours, but include jquery.bootstrap.modal.forms.js exactly as in code bellow.
<head>
<linkrel="stylesheet"href="{% static 'assets/css/bootstrap.css' %}">
</head>
<body>
<scriptsrc="{% static 'assets/js/bootstrap.js' %}"></script>
<scriptsrc="{% static 'assets/js/jquery.js' %}"></script>
<scriptsrc="{% static 'js/jquery.bootstrap.modal.forms.js' %}"></script>
<!-- You can alternatively load the minified version -->
<scriptsrc="{% static 'js/jquery.bootstrap.modal.forms.min.js' %}"></script>
</body>index.html
<scripttype="text/javascript">$(document).ready(function(){$("#create-book").modalForm({formURL: "{% url 'create_book' %}"});});</script>- Click event on html element instantiated with
modalFormopens modal - Form at
formURLis appended to the modal - On submit the form is POSTed via AJAX request to
formURL - Unsuccessful POST request returns errors, which are shown in modal
- Successful POST request submits the form and redirects to
success_urland showssuccess_message, which are both defined in related Django view
Define BookModelForm and inherit built-in form BSModalModelForm.
forms.pyfrom .modelsimportBookfrombootstrap_modal_forms.formsimportBSModalModelFormclassBookModelForm(BSModalModelForm):
classMeta:
model=Bookfields= ['title', 'author', 'price']Define form's html and save it as Django template.
- Bootstrap 4 modal elements are used in this example.
- Form will POST to
formURLdefined in #6. - Add
class="invalid"or customerrorClass(see paragraph Options) to the elements that wrap the fields. class="invalid"acts as a flag for the fields having errors after the form has been POSTed.
book/create_book.html
<formmethod="post" action="">
{% csrf_token %}
<divclass="modal-header"><h5class="modal-title">Create new Book</h5><buttontype="button" class="close" data-dismiss="modal" aria-label="Close"><spanaria-hidden="true">×</span></button></div><divclass="modal-body">
{% for field in form %}
<divclass="form-group{% if field.errors %} invalid{% endif %}"><labelfor="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
{% for error in field.errors %}
<pclass="help-block">{{ error }}</p>
{% endfor %}
</div>
{% endfor %}
</div><divclass="modal-footer"><buttontype="button" class="btn btn-default" data-dismiss="modal">Close</button><buttontype="submit" class="btn btn-primary">Create</button></div></form>Define a class-based view BookCreateView and inherit from built-in generic view BSModalCreateView. BookCreateView processes the form defined in #1, uses the template defined in #2 and redirects to success_url showing success_message.
views.pyfromdjango.urlsimportreverse_lazyfrom .formsimportBookModelFormfrom .modelsimportBookfrombootstrap_modal_forms.genericimportBSModalCreateViewclassBookCreateView(BSModalCreateView):
template_name='examples/create_book.html'form_class=BookModelFormsuccess_message='Success: Book was created.'success_url=reverse_lazy('index')Define URL for the view in #3.
fromdjango.urlsimportpathfrombooksimportviewsurlpatterns= [
path('', views.Index.as_view(), name='index'),
path('create/', views.BookCreateView.as_view(), name='create_book'),
]Define the Bootstrap modal window and html element triggering modal opening.
- Single modal can be used for multiple
modalFormsin single template (see #6). - When using multiple modals on the same page each modal should have unique
idand the same value should also be set asmodalIDoption when instantiatingmodalFormon trigger element. - Trigger element (in this example button with
id="create-book") is used for instantiation ofmodalFormin #6. - Any element can be trigger element as long as
modalFormis bound to it. - Click event on trigger element loads form's html from #2 within
<div class="modal-content"></div>and sets action attribute of the form toformURLset in #6.
index.html
<divclass="modal fade"tabindex="-1"role="dialog"id="modal">
<divclass="modal-dialog"role="document">
<divclass="modal-content"></div>
</div>
</div>
<!-- Create book button -->
<buttonid="create-book"class="btn btn-primary"type="button"name="button">Create book</button>Add script to the template from #5 and bind the modalForm to the trigger element. Set BookCreateView URL defined in #4 as formURL property of modalForm.
- If you want to create more modalForms in single template using the single modal window from #5, repeat steps #1 to #4, create new trigger element as in #5 and bind the new
modalFormwith unique URL to it. - Default values for
modalID,modalContent,modalFormanderrorClassare used in this example, whileformURLis customized. If you customize any other option adjust the code of the above examples accordingly.
index.html
<scripttype="text/javascript">$(document).ready(function(){$("#create-book").modalForm({formURL: "{% url 'create_book' %}"});});</script>Set asyncUpdate and asyncSettings settings to create or update objects without page redirection to successUrl and define whether a modal should close or stay opened after form submission. See comments in example below and paragraph modalForm options for explanation of asyncSettings.
See examples on how to properly reinstantiate modal forms for all CRUD buttons when using async options.
index.html
<!-- asyncSettings.dataElementId --><tableid="books-table" class="table"><thead>
...
</thead><tbody>
{% for book in books %}
<tr>
...
<!-- Update book buttons --><buttontype="button" class="update-book btn btn-sm btn-primary" data-form-url="{% url 'update_book' book.pk %}"><spanclass="fa fa-pencil"></span></button>
...
</td></tr>
{% endfor %}
</tbody></table><scripttype="text/javascript">$(function(){
...
# asyncSettings.successMessagevarasyncSuccessMessage=["<div ","style='position:fixed;top:0;z-index:10000;width:100%;border-radius:0;' ","class='alert alert-icon alert-success alert-dismissible fade show mb-0' role='alert'>","Success: Book was updated.","<button type='button' class='close' data-dismiss='alert' aria-label='Close'>","<span aria-hidden='true'>×</span>","</button>","</div>","<script>","$('.alert').fadeTo(2000, 500).slideUp(500, function () {$('.alert').slideUp(500).remove();});","<\/script>"].join();
# asyncSettings.addModalFormFunctionfunctionupdateBookModalForm(){$(".update-book").each(function(){$(this).modalForm({formURL: $(this).data("form-url"),asyncUpdate: true,asyncSettings: {closeOnSubmit: false,successMessage: asyncSuccessMessagedataUrl: "books/",dataElementId: "#books-table",dataKey: "table",addModalFormFunction: updateBookModalForm}});});}updateBookModalForm();
...
});</script>urls.pyfromdjango.urlsimportpathfrom . importviewsurlpatterns= [
...
# asyncSettings.dataUrlpath('books/', views.books, name='books'),
...
]views.pyfromdjango.httpimportJsonResponsefromdjango.template.loaderimportrender_to_stringfrom .modelsimportBookdefbooks(request):
data=dict()
ifrequest.method=='GET':
books=Book.objects.all()
# asyncSettings.dataKey = 'table'data['table'] =render_to_string(
'_books_table.html',
{'books': books},
request=request
)
returnJsonResponse(data)- modalID
- Sets the custom id of the modal.
Default: "#modal" - modalContent
- Sets the custom class of the element to which the form's html is appended. If you change
modalContentto the custom class, you should also changemodalFormaccordingly. To keep Bootstrap's modal style you should than copy Bootstrap's style formodal-contentand set it to your new modalContent class.Default: ".modal-content" - modalForm
- Sets the custom form selector.
Default: ".modal-content form" - formURL
- Sets the url of the form's view and html.
Default: null - isDeleteForm
- Defines if form is used for deletion. Should be set to
truefor deletion forms.Default: false - errorClass
- Sets the custom class for the form fields having errors.
Default: ".invalid" - asyncUpdate
- Sets asynchronous content update after form submission.
Default: false - asyncSettings.closeOnSubmit
- Sets whether modal closes or not after form submission.
Default: false - asyncSettings.successMessage
- Sets successMessage shown after succesful for submission. Should be set to string defining message element. See
asyncSuccessMessageexample above.Default: null - asyncSettings.dataUrl
- Sets url of the view returning new queryset = all of the objects plus newly created or updated one after asynchronous update.
Default: null - asyncSettings.dataElementId
- Sets the
idof the element which rerenders asynchronously updated queryset.Default: null - asyncSettings.dataKey
- Sets the key containing asynchronously updated queryset in the data dictionary returned from the view providing updated queryset.
Default: null - asyncSettings.addModalFormFunction
- Sets the method needed for reinstantiation of event listeners on buttons (single or all CRUD buttons) after asynchronous update.
Default: null
triggerElement.modalForm({
modalID: "#modal",
modalContent: ".modal-content",
modalForm: ".modal-content form",
formURL: null,
isDeleteForm: false,
errorClass: ".invalid",
asyncUpdate: false,
asyncSettings: {
closeOnSubmit: false,
successMessage: null,
dataUrl: null,
dataElementId: null,
dataKey: null,
addModalFormFunction: null
}
});Import forms with from bootstrap_modal_forms.forms import BSModalForm.
- BSModalForm
- Inherits PopRequestMixin and Django's forms.Form.
- BSModalModelForm
- Inherits PopRequestMixin, CreateUpdateAjaxMixin and Django's forms.ModelForm.
Import mixins with from bootstrap_modal_forms.mixins import PassRequestMixin.
- PassRequestMixin
- Puts the request into the form's kwargs.
- PopRequestMixin
- Pops request out of the kwargs and attaches it to the form's instance.
- CreateUpdateAjaxMixin
- Saves or doesn't save the object based on the request type.
- DeleteMessageMixin
- Deletes object if request is not ajax request.
- LoginAjaxMixin
- Authenticates user if request is not ajax request.
Import generic views with from bootstrap_modal_forms.generic import BSModalFormView.
- BSModalFormView
- Inherits PassRequestMixin and Django's generic.FormView.
- BSModalCreateView
- Inherits PassRequestMixin and Django's SuccessMessageMixin and generic.CreateView.
- BSModalUpdateView
- Inherits PassRequestMixin and Django's SuccessMessageMixin and generic.UpdateView.
- BSModalReadView
- Inherits Django's generic.DetailView.
- BSModalDeleteView
- Inherits DeleteMessageMixin and Django's generic.DeleteView.
To see django-bootstrap-modal-forms in action clone the repository and run the examples locally:
$ git clone https://github.com/trco/django-bootstrap-modal-forms.git $ cd django-bootstrap-modal-forms $ pip install -r requirements.txt $ python manage.py migrate $ python manage.py runserver
Run unit and functional tests inside of project folder:
$ python manage.py test
For explanation how all the parts of the code work together see paragraph Usage. To test the working solution presented here clone and run Examples.
forms.pyfromdjango.contrib.auth.formsimportUserCreationFormfromdjango.contrib.auth.modelsimportUserfrombootstrap_modal_forms.mixinsimportPopRequestMixin, CreateUpdateAjaxMixinclassCustomUserCreationForm(PopRequestMixin, CreateUpdateAjaxMixin,
UserCreationForm):
classMeta:
model=Userfields= ['username', 'password1', 'password2']signup.html
{% load widget_tweaks %}
<formmethod="post" action="">
{% csrf_token %}
<divclass="modal-header"><h3class="modal-title">Sign up</h3><buttontype="button" class="close" data-dismiss="modal" aria-label="Close"><spanaria-hidden="true">×</span></button></div><divclass="modal-body"><divclass="{% if form.non_field_errors %}invalid{% endif %} mb-2">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% for field in form %}
<divclass="form-group"><labelfor="{{ field.id_for_label }}">{{ field.label }}</label>
{% render_field field class="form-control" placeholder=field.label %}
<divclass="{% if field.errors %} invalid{% endif %}">
{% for error in field.errors %}
<pclass="help-block">{{ error }}</p>
{% endfor %}
</div></div>
{% endfor %}
</div><divclass="modal-footer"><buttontype="submit" class="btn btn-primary">Sign up</button></div></form>views.pyfromdjango.urlsimportreverse_lazyfrombootstrap_modal_forms.genericimportBSModalCreateViewfrom .formsimportCustomUserCreationFormclassSignUpView(BSModalCreateView):
form_class=CustomUserCreationFormtemplate_name='examples/signup.html'success_message='Success: Sign up succeeded. You can now Log in.'success_url=reverse_lazy('index')urls.pyfromdjango.urlsimportpathfrom . importviewsapp_name='accounts'urlpatterns= [
path('signup/', views.SignUpView.as_view(), name='signup')
].html file containing modal, trigger element and script instantiating modalForm
<divclass="modal fade" tabindex="-1" role="dialog" id="modal"><divclass="modal-dialog" role="document"><divclass="modal-content"></div></div></div><buttonid="signup-btn" class="btn btn-primary" type="button" name="button">Sign up</button><scripttype="text/javascript">$(function(){// Sign up button$("#signup-btn").modalForm({formURL: "{% url 'signup' %}"});});</script>For explanation how all the parts of the code work together see paragraph Usage. To test the working solution presented here clone and run Examples.
You can set the login redirection by setting the LOGIN_REDIRECT_URL in settings.py.
You can also set the custom login redirection by:
- Adding
success_urlto theextra_contextofCustomLoginView - Setting this
success_urlvariable as a value of thehidden input fieldwithname="next"within the Login form html
forms.pyfromdjango.contrib.auth.formsimportAuthenticationFormfromdjango.contrib.auth.modelsimportUserclassCustomAuthenticationForm(AuthenticationForm):
classMeta:
model=Userfields= ['username', 'password']login.html
{% load widget_tweaks %}
<formmethod="post" action="">
{% csrf_token %}
<divclass="modal-header"><h3class="modal-title">Log in</h3><buttontype="button" class="close" data-dismiss="modal" aria-label="Close"><spanaria-hidden="true">×</span></button></div><divclass="modal-body"><divclass="{% if form.non_field_errors %}invalid{% endif %} mb-2">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% for field in form %}
<divclass="form-group"><labelfor="{{ field.id_for_label }}">{{ field.label }}</label>
{% render_field field class="form-control" placeholder=field.label %}
<divclass="{% if field.errors %} invalid{% endif %}">
{% for error in field.errors %}
<pclass="help-block">{{ error }}</p>
{% endfor %}
</div></div>
{% endfor %}
<!-- Hidden input field for custom redirection after successful login --><inputtype="hidden" name="next" value="{{ success_url }}"></div><divclass="modal-footer"><buttontype="submit" class="btn btn-primary">Log in</button></div></form>views.pyfromdjango.urlsimportreverse_lazyfrombootstrap_modal_forms.genericimportBSModalLoginViewfrom .formsimportCustomAuthenticationFormclassCustomLoginView(BSModalLoginView):
authentication_form=CustomAuthenticationFormtemplate_name='examples/login.html'success_message='Success: You were successfully logged in.'extra_context=dict(success_url=reverse_lazy('index'))urls.pyfromdjango.urlsimportpathfrom . importviewsapp_name='accounts'urlpatterns= [
path('login/', views.CustomLoginView.as_view(), name='login')
].html file containing modal, trigger element and script instantiating modalForm
<divclass="modal fade" tabindex="-1" role="dialog" id="modal"><divclass="modal-dialog" role="document"><divclass="modal-content"></div></div></div><buttonid="login-btn" class="btn btn-primary" type="button" name="button">Sign up</button><scripttype="text/javascript">$(function(){// Log in button$("#login-btn").modalForm({formURL: "{% url 'login' %}"});});</script>For explanation how all the parts of the code work together see paragraph Usage. To test the working solution presented here clone and run Examples.
forms.pyfrom .modelsimportBookfrombootstrap_modal_forms.formsimportBSModalModelFormclassBookModelForm(BSModalModelForm):
classMeta:
model=Bookexclude= ['timestamp']create_book.html
{% load widget_tweaks %}
<formmethod="post" action="">
{% csrf_token %}
<divclass="modal-header"><h3class="modal-title">Create Book</h3><buttontype="button" class="close" data-dismiss="modal" aria-label="Close"><spanaria-hidden="true">×</span></button></div><divclass="modal-body"><divclass="{% if form.non_field_errors %}invalid{% endif %} mb-2">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% for field in form %}
<divclass="form-group"><labelfor="{{ field.id_for_label }}">{{ field.label }}</label>
{% render_field field class="form-control" placeholder=field.label %}
<divclass="{% if field.errors %} invalid{% endif %}">
{% for error in field.errors %}
<pclass="help-block">{{ error }}</p>
{% endfor %}
</div></div>
{% endfor %}
</div><divclass="modal-footer"><buttontype="submit" class="btn btn-primary">Create</button></div></form>update_book.html
{% load widget_tweaks %}
<formmethod="post" action="">
{% csrf_token %}
<divclass="modal-header"><h3class="modal-title">Update Book</h3><buttontype="button" class="close" data-dismiss="modal" aria-label="Close"><spanaria-hidden="true">×</span></button></div><divclass="modal-body"><divclass="{% if form.non_field_errors %}invalid{% endif %} mb-2">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% for field in form %}
<divclass="form-group"><labelfor="{{ field.id_for_label }}">{{ field.label }}</label>
{% render_field field class="form-control" placeholder=field.label %}
<divclass="{% if field.errors %} invalid{% endif %}">
{% for error in field.errors %}
<pclass="help-block">{{ error }}</p>
{% endfor %}
</div></div>
{% endfor %}
</div><divclass="modal-footer"><buttontype="submit" class="btn btn-primary">Update</button></div></form>read_book.html
{% load widget_tweaks %}
<divclass="modal-header"><h3class="modal-title">Book details</h3><buttontype="button" class="close" data-dismiss="modal" aria-label="Close"><spanaria-hidden="true">×</span></button></div><divclass="modal-body"><divclass="">
Title: {{ book.title }}
</div><divclass="">
Author: {{ book.author }}
</div><divclass="">
Price: {{ book.price }} €
</div></div><divclass="modal-footer"><buttontype="button" class="btn btn-default" data-dismiss="modal">Close</button></div>{% load widget_tweaks %}
<formmethod="post" action="">
{% csrf_token %}
<divclass="modal-header"><h3class="modal-title">Delete Book</h3><buttontype="button" class="close" data-dismiss="modal" aria-label="Close"><spanaria-hidden="true">×</span></button></div><divclass="modal-body"><p>Are you sure you want to delete book with title
<strong>{{ book.title }}</strong>?</p></div><divclass="modal-footer"><buttontype="submit" class="btn btn-danger">Delete</button></div></form>views.pyfromdjango.urlsimportreverse_lazyfromdjango.viewsimportgenericfrom .formsimportBookModelFormfrom .modelsimportBookfrombootstrap_modal_forms.genericimport (
BSModalCreateView,
BSModalUpdateView,
BSModalReadView,
BSModalDeleteView
)
classIndex(generic.ListView):
model=Bookcontext_object_name='books'template_name='index.html'# CreateclassBookCreateView(BSModalCreateView):
template_name='examples/create_book.html'form_class=BookModelFormsuccess_message='Success: Book was created.'success_url=reverse_lazy('index')
# UpdateclassBookUpdateView(BSModalUpdateView):
model=Booktemplate_name='examples/update_book.html'form_class=BookModelFormsuccess_message='Success: Book was updated.'success_url=reverse_lazy('index')
# ReadclassBookReadView(BSModalReadView):
model=Booktemplate_name='examples/read_book.html'# DeleteclassBookDeleteView(BSModalDeleteView):
model=Booktemplate_name='examples/delete_book.html'success_message='Success: Book was deleted.'success_url=reverse_lazy('index')urls.pyfromdjango.urlsimportpathfrombooksimportviewsurlpatterns= [
path('', views.Index.as_view(), name='index'),
path('create/', views.BookCreateView.as_view(), name='create_book'),
path('update/<int:pk>', views.BookUpdateView.as_view(), name='update_book'),
path('read/<int:pk>', views.BookReadView.as_view(), name='read_book'),
path('delete/<int:pk>', views.BookDeleteView.as_view(), name='delete_book')
].html file containing modal, trigger elements and script instantiating modalForms
<!-- Modal 1 with id="create-book"--><divclass="modal fade" id="create-modal" tabindex="-1" role="dialog" aria-hidden="true"><divclass="modal-dialog"><divclass="modal-content"></div></div></div><!-- Modal 2 with id="modal" --><divclass="modal fade" tabindex="-1" role="dialog" id="modal"><divclass="modal-dialog" role="document"><divclass="modal-content"></div></div></div><!-- Create book button --><buttonid="create-book" class="btn btn-primary" type="button" name="button">Create book</button>
{% for book in books %}
<divclass="text-center"><!-- Read book buttons --><buttontype="button" class="read-book bs-modal btn btn-sm btn-primary" data-form-url="{% url 'read_book' book.pk %}"><spanclass="fa fa-eye"></span></button><!-- Update book buttons --><buttontype="button" class="update-book bs-modal btn btn-sm btn-primary" data-form-url="{% url 'update_book' book.pk %}"><spanclass="fa fa-pencil"></span></button><!-- Delete book buttons --><buttontype="button" class="delete-book bs-modal btn btn-sm btn-danger" data-form-url="{% url 'delete_book' book.pk %}"><spanclass="fa fa-trash"></span></button></div>
{% endfor %}
<scripttype="text/javascript">$(function(){// Read book buttons$(".read-book").each(function(){$(this).modalForm({formURL: $(this).data("form-url")});});// Delete book buttons - formURL is retrieved from the data of the element$(".delete-book").each(function(){$(this).modalForm({formURL: $(this).data("form-url"),isDeleteForm: true});});// Create book button opens form in modal with id="create-modal"$("#create-book").modalForm({formURL: "{% url 'create_book' %}",modalID: "#create-modal"});});</script>- See the difference between button triggering Create action and buttons triggering Read, Update and Delete actions.
- Within the for loop in .html file the
data-form-urlattribute of each Update, Read and Delete button should be set to relevant URL with pk argument of the object to be updated, read or deleted. - These
data-form-urlURLs should than be set asformURLsformodalFormsbound to the buttons.
For explanation how all the parts of the code work together see paragraph Usage. To test the working solution presented here clone and run Examples.
forms.pyfrombootstrap_modal_forms.formsimportBSModalFormclassBookFilterForm(BSModalForm):
type=forms.ChoiceField(choices=Book.BOOK_TYPES)
classMeta:
fields= ['type']filter_book.html
{% load widget_tweaks %}
<formmethod="post" action="">
{% csrf_token %}
<divclass="modal-header"><h3class="modal-title">Filter Books</h3><buttontype="button" class="close" data-dismiss="modal" aria-label="Close"><spanaria-hidden="true">×</span></button></div><divclass="modal-body"><divclass="{% if form.non_field_errors %}invalid{% endif %} mb-2">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% for field in form %}
<divclass="form-group"><labelfor="{{ field.id_for_label }}">{{ field.label }}</label>
{% render_field field class="form-control" placeholder=field.label %}
<divclass="{% if field.errors %} invalid{% endif %}">
{% for error in field.errors %}
<pclass="help-block">{{ error }}</p>
{% endfor %}
</div></div>
{% endfor %}
</div><divclass="modal-footer"><buttontype="submit" class="btn btn-primary">Filter</button></div></form>views.pyclassBookFilterView(BSModalFormView):
template_name='examples/filter_book.html'form_class=BookFilterFormdefform_valid(self, form):
self.filter='?type='+form.cleaned_data['type']
response=super().form_valid(form)
returnresponsedefget_success_url(self):
returnreverse_lazy('index') +self.filterurls.pyfromdjango.urlsimportpathfrom . importviewsapp_name='accounts'urlpatterns= [
path('filter/', views.BookFilterView.as_view(), name='filter_book'),
]index.html
...
<buttonid="filter-book" class="filter-book btn btn-primary" type="button" name="button" data-form-url="{% url 'filter_book' %}"><spanclass="fa fa-filter mr-2"></span>Filter books
</button>
...
<scripttype="text/javascript">$(function(){
...
$("#filter-book").each(function(){$(this).modalForm({formURL: $(this).data('form-url')});});
...
});</script>This is an Open Source project and any contribution is appreciated.
This project is licensed under the MIT License.