Using Ajax to create asynchronous request to manipulate Django models is a very common use case. It can be used to
provide an in line edit in a table, or create a new model instance without going back and forth in the website.
It also bring some challenges, such as keeping the state of the objects consistent.
In case you are not familiar with the term CRUD, it stand for Create Read Update Delete.
Those are the basic operations we perform in the application entities. For the most part the Django Admin is all about
CRUD.
This tutorial is compatible with Python 2.7 and 3.5, using Django 1.8, 1.9 or 1.10.
For this tutorial we will be using jQuery to implement the Ajax requests. Feel free to use any other JavaScript
framework (or to implement it using bare JavaScript). The concepts should remain the same.
Grab a copy of jQuery, either download it or refer to one of the many CDN options.
I usually like to have a local copy, because sometimes I have to work off-line. Place the jQuery in the bottom of your
base template:
base.html
I will be also using Bootstrap. It is not required but it provide a good base css and also some useful HTML components,
such as a Modal and pretty tables.
Working Example
I will be working in a app called books. For the CRUD operations consider the following model:
models.py
Listing Books
Let’s get started by listing all the book objects.
We need a route in the urlconf:
urls.py:
A simple view to list all the books:
views.py
book_list.html
So far nothing special. Our template should look like this:
Create Book
First thing, let’s create a model form. Let Django do its work.
forms.py
We need now to prepare the template to handle the creation operation. We will be working with partial templates to
render only the parts that we actually need.
The strategy I like to use is to place a generic bootstrap modal, and use it for all the operations.
book_list.html
Note that I already added a button that will be used to start the creation process. I added a class js-create-book
to hook the click event. I usually add a class starting with js- for all elements that interacts with JavaScript
code. It’s easier to debug the code later on. It’s not a enforcement but just a convention. Helps the code quality.
Add a new route:
urls.py:
Let’s implement the book_create view:
views.py
Note that we are not rendering a template but returning a Json response.
Now we create the partial template to render the form:
partial_book_create.html
I’m using the django-widget-tweaks library to render the form fields properly using the bootstrap class.
You can read more about it in a post I published last year: Package of the Week: Django Widget Tweaks.
Now the glue that will put everything together: JavaScript.
Create an external JavaScript file. I created mine in the path: mysite/books/static/books/js/books.js
books.js
Don’t forget to include this JavaScript file in the book_list.html template:
book_list.html
Let’s explore the JavaScript snippet in great detail:
This is a jQuery shortcut to tell the browser to wait for all the HTML be rendered before executing the code:
Here we are hooking into the click event of the element with class js-create-book, which is our Add book button.
When the user clicks in the js-create-book button, this anonymous function with the $.ajax call will be
executed:
Now, what is this ajax request saying to the browser:
Hey, the resource I want is in this path:
Make sure you request my data using the HTTP GET method:
Oh, by the way, I want to receive the data in JSON format:
But just before you communicate with the server, please execute this code:
(This will open the Bootstrap Modal before the Ajax request starts.)
And right after you receive the data (in the data variable), execute this code:
(This will render the partial form defined in the partial_book_create.html template.)
Let’s have a look on what we have so far:
Then when the user clicks the button:
Great stuff. The book form is being rendered asynchronously. But it is not doing much at the moment. Good news is that
the structure is ready, now it is a matter of playing with the data.
Let’s implement now the form submission handling.
First let’s improve the book_create view function:
views.py
partial_book_create.html
I added the action attribute to tell the browser to where it should send the submission and the class
js-book-create-form for us to use in the JavaScript side, hooking on the form submit event.
books.js
The way we are listening to the submit event is a little bit different from what we have implemented before. That’s
because the element with class .js-book-create-form didn’t exist on the initial page load of the book_list.html
template. So we can’t register a listener to an element that doesn’t exists.
A work around is to register the listener to an element that will always exist in the page context. The #modal-book
is the closest element. It is a little bit more complex what happen, but long story short, the HTML events propagate
to the parents elements until it reaches the end of the document.
Hooking to the body element would have the same effect, but it would be slightly worst, because it would have to
travel through several HTML elements before reaching it. So always pick the closest one.
Now the actual function:
books.js
A very important detail here: in the end of the function we are returning false. That’s because we are capturing
the form submission event. So to avoid the browser to perform a full HTTP POST to the server, we cancel the default
behavior returning false in the function.
So, what we are doing here:
In this context, this refers to the element with class .js-book-create-form. Which is the element that fired
the submit event. So when we select $(this) we are selecting the actual form.
Now I’m using the form attributes to build the Ajax request. The action here refers to the action attribute in
the form, which translates to /books/create/.
As the name suggests, we are serializing all the data from the form, and posting it to the server. The rest follows
the same concepts as I explained before.
Before we move on, let’s have a look on what we have so far.
The user fills the data:
The user clicks on the Create book button:
The data was invalid. No hard refresh no anything. Just this tiny part changed with the validation. This is what
happened:
The form was submitted via Ajax
The view function processed the form
The form data was invalid
The view function rendered the invalid stated to the data['html_form'], using the render_to_string
The Ajax request returned to the JavaScript function
The Ajax success callback was executed, replacing the contents of the modal with the new data['html_form']
Please note that the Ajax success callback:
Refers to the status of the HTTP Request, which has nothing to do with the status of your form, or whether the form
was successfully processed or not. It only means that the HTTP Request returned a status 200 for example.
Let’s fix the publication date value and submit the form again:
There we go, the alert tells us that the form was successfully processed and hopefully it was created in the
database.
It is not 100% what we want, but we are getting close. Let’s refresh the screen and see if the new book shows in the
table:
Great. We are getting there.
What we want to do now: after the success form processing, we want to close the bootstrap modal and update the table
with the newly created book. For that matter we will extract the body of the table to a external partial template,
and we will return the new table body in the Ajax success callback.
Watch that:
book_list.html
partial_book_list.html
Now we can reuse the partial_book_list.html snippet without repeating ourselves.
Next step: book_create view function.
views.py
A proper success handler in the JavaScript side:
books.js
Sweet. It’s working!
Edit Book
As you can expect, this will be very similar to what we did on the Create Book section. Except we will need to pass
the ID of the book we want to edit. The rest should be somewhat the same. We will be reusing several parts of the code.
urls.py:
Now we refactor the book_create view to reuse its code in the book_update view:
views.py
Basically the view functions book_create and book_update are responsible for receiving the request, preparing
the form instance and passing it to the save_book_form, along with the name of the template to use in the rendering
process.
Next step is to create the partial_book_update.html template. Similar to what we did with the view functions, we
will also refactor the partial_book_create.html to reuse some of the code.
partial_book_form.html
partial_book_create.html
partial_book_update.html
This is good enough. Now we gotta add an edit button to trigger the action.
partial_book_list.html
The class js-update-book will be used to start the edit process. Now note that I also added an extra HTML attribute
named data-url. This is the URL that will be used to create the ajax request dynamically.
Take the time and refactor the js-create-book button to also use the data-url strategy, so we can extract
the hard-coded URL from the Ajax request.
book_list.html
books.js
Next step is to create the edit functions. The thing is, they are pretty much the same as the create. So, basically
what we want to do is to extract the anonymous functions that we are using, and reuse them in the edit buttons and
forms. Check it out:
books.js
Let’s have a look on what we have so far.
The user clicks in the edit button.
Changes some data like the title of the book and hit the Update book button:
Cool! Now just the delete and we are done.
Delete Book
urls.py:
views.py
partial_book_delete.html
partial_book_list.html
books.js
And the result will be:
The user clicks in a Delete button:
The user confirm the deletion, and the table in the background is refreshed:
Conclusions
I decided to use function-based views in this example because they are easier to read and use less configuration-magic
that could distract those who are learning Django.
I tried to bring as much detail as I could and discuss about some of the code design decisions. If anything is not
clear to you, or you want to suggest some improvements, feel free to leave a comment! I would love to hear your
thoughts.
The code is available on GitHub: github.com/sibtc/simple-ajax-crud,
so you can try it locally. It’s very straightforward to get it running.