Here are the additional resources referenced in the lesson:
SPA Code Walk Through
In the previous lesson, I showed you an example of an SPA architecture for the sample
Sched application. In this lesson, I’m going to dig in deeply and show you some of the code that goes along with it. Even for a relatively small application, there’s a fair number of moving parts here.
01:01 Don’t forget all of this is available inside of the supplementary materials, and you can play with it on your own if it helps you understand.
If you’re going to run the sample code for yourself, you’re going to need a couple things. First off, you’ll need to install the dependencies. The dependencies are listed inside of
In order to keep your Python environment clean, it’s best practice to use a virtual environment for this. You can install all of the
requirements.txt file by using the
-r parameter to the
pip install command.
01:33 This’ll take everything inside of the text file and install all of it.
Inside of the
Sched/ directory, you’ll find a shell script that you can use to create the database, do the migrations, and set everything up.
01:47 This is a Bash shell script. It will only work if you are using some sort of Unix derivative, so if you’re on a Mac, on Linux, or you’re using the Windows Linux subsystem, then you can use this command as a shortcut.
02:01 Here’s the contents of that script. If you happen to be running on regular Windows and you can’t use this script, then type these six commands in and you’ll be good to go.
Actually, you’ll only need the last four, because if you’ve got a brand new environment, there won’t be any PYC files or a SQLite file to get rid of. The first command,
wipe_migrations, comes out of
django-awl. It destroys any migration files.
02:28 I use this command when I’m playing with a brand new project so that I don’t create overly complicated chains of migration history when I don’t really care about the migration history because I haven’t gone into production yet. So it starts me clean each time.
migrate will set up and run your database, and then
loaddata is used to grab the sample data for the timelines.
02:50 The first half of this script gets rid of anything that’s there already, so if you play around and you want to start from scratch, run the script again.
Let me show you what this application looks like in the browser before I show you the code. Offscreen, I’m running the Django dev server, and here I’m going to hit the homepage of
03:11 Here’s the first of my two pages of my single-page application. This one is a standard Django view that spits out a simple Bootstrap listing of the timelines that are in the database.
03:23 Clicking on Peru takes me into the second page of my single-page application, and this one’s a little more SPA-ish. This is built with Vue.js. Each row in the schedule corresponds to an item in the database. All of the rows are being sent down via JSON and then interpreted by Vue.js.
03:46 I can click the Add button and get a popup for entering new information.
03:54 This popup is one of the Bootstrap modal popups coming from that library that I told you about. The popup is REST-aware. This form inherits from a base class that knows how to create things in REST.
It uses the names of the fields inside of the form and populates an AJAX call up to the server with a
POST method. By doing this, I need very little code to be able to create new objects on the server.
I just need the form to create them and to make sure that the
name attributes of the form are properly set.
04:28 Submitting the form does a call to the server and updates what’s on the screen.
Clicking on one of these objects pops up the same form. Now the same form can be used to do an update. The
bsmodals library takes care of all of this for you.
Clicking the Delete button brings up a confirmation dialog. These dialogs are also part of
DELETE on the object in question.
05:04 I’ll show you the code to that shortly. And finally, this application is drag-and-drop-enabled. The handles on the left-hand side of the screen allow me to change the order of the items.
This no longer follows the movie, but you see what I’m talking about. The movement updates the rank of the item, affecting the order on the server, and this has to be done with a custom action in order to take advantage of how
RankedModel work. Here are the models for the application.
There’s two things in the database. One, which is a timeline, which is named and groups a series of entries, and the other is the entries. Lines 6 through 10 defines the
Timeline and the
Entry object starts on line 12. Let me just scroll down here for you.
Entry has four fields. Three of them are defined here,
length, and the
timeline that’s associated with it.
And the fourth is hidden in the abstract model called
RankedModel contains a field called
rank. This is an integer that is
1 or more and indicates the order of the entry.
Inside of the
Meta class, I specify the ordering as the
"rank" field to make sure that when these are queried, they come back in rank order.
views file that defines the two pages in my single-page application. Line 6 is the homepage that lists all of the timelines, and line 14 boots up the single-page application part of this single-page application.
edit_timeline() view takes an ID, which is the timeline that is being edited, and then it renders the
06:56 That file, amongst other things, contains the information that Vue.js needs to render the page.
Up until now, I’ve been showing you the core app, which is for the timelines. I’ve also created another app called the
api. I’m grouping all of the things that’ll be available through REST inside of the Django
That includes the
serializers.py file. Inside of the
serializers file, I have two serializers—one for the timelines and one for the entries.
TimelineSerializer contains the entries within it, so I’m nesting
Entry objects inside of the timeline. Here’s another use of the
source attribute. The name
entry_set might be a little confusing inside of the HTML or for anyone who isn’t familiar with Django, so I’ve changed the serializer to call the entries
EntrySerializer then has the
source parameter of
"entry_set", telling the DRF that the
entries field should be populated based on
entry_set of the
Because there are multiple entries,
many is set to
True. And so that I can create a
Timeline without specifying and serializing entries to go with it, I’ve set
read_only equal to
True as well.
The entry itself contains the
"id" and the
"url" of the
"rank" of the
Entry. It also contains the
This will be the
id of the foreign key of the
Timeline object. This is necessary because you can’t have an entry without it pointing at a timeline, so when you go to create a new
Entry, you’re going to need that
Inside of the
api/views file, I define the ViewSets for the DRF views that I’m going to use in the application. The first two items here are the vanilla ViewSets for the
Timeline and the
Entry. I’ve specified their corresponding serializer classes and the corresponding querysets.
Let me just scroll down. The abstract
RankedModel class has logic inside of it that ensures that the ranks stay in order. The logic of all of this is done inside of an overridden
The DRF does not call the
.save() method on an object. It directly updates the database. Any logic in an overridden
.save() is not going to get executed. As a result, I’ve defined a new view here that is specific to changing the rank.
I’m not going to use the built-in update methods of the DRF—I’m going to update it myself manually to ensure that the
Entry.save() method gets called. Except for this being a special case as to why I’m doing it, the rest of it is a view like you’ve seen before.
I’m using the
@api_view decorator to indicate that this is a
GET. This is a little hack-ish. To be more technically correct, I probably should have made this a
PUT or a
PATCH, because I am editing an entry.
But the URL that I’m going to be using for
change_rank() will include the ID of the entry and its new rank. Because I’m doing this inside of the URL, I’m sticking with a
This is one of those many cases where REST is kind of a suggestion rather than a rule. Using the
entry_id, I get the object. I change the
.rank to the
new_rank provided and call the overridden
10:36 I then re-query all of the entries that are associated with this timeline, and serialize them, sending them back down. This means when a move has been executed the Vue.js application can get all the data at once as to what the new ranks are of all the entries that were affected.
This is part of the
edit_timeline.html file. The
I’ve done this through the
bsmodals dialogs, a utilities function that deals with CSRF prevention, the actual Vue.js application inside of
timelines.js, and a third-party library that allows me to do drag-and-drop with Vue.js.
This is the
timelines.js file. This is where the master object for Vue.js is created. That’s on line 4, and it’s put in a variable called
You’ll see the use of that variable throughout this file. In lines 8 through 11, I create the data for the Vue.js object.
.has_loaded is used to indicate whether or not the screen should be rendered yet.
entries are all the JSON that come from the server—
name being from the
Timeline object, and
entries being the array of JSON that specify the entries on the display.
Django has a whole bunch of security mechanisms built-in, one of which is to prevent problems with form posts. This is done through the CSRF token. The
get_cookie() methods come out of the
utils.js file and are used to make sure that a
POST can happen.
13:12 All Django posts must include a CSRF token, so this little bit of code gets that token in the right place.
Once everything’s loaded on the page, the last call here is to
refresh_view(). This method is what actually does the REST call. Inside of
refresh_view(), I use jQuery’s
.getJSON() method, passing in that global
.getJSON() takes a
response() function, and inside the body of that
response() function, I’m setting up the Vue.js data attributes.
.name both come out of the
response field from the timeline object, and
.entries is copied from the array of
Once those three values have been put inside of the Vue.js object, I set
true to get the screen to render.
The Vue.js template that is rendered included the Add button. There’s a hook on the Add button that says when the button is pushed, call the method
That’s defined here on line 14. Inside of the
add_entry(), I create a dictionary that contains the ID of the timeline object for the entry that’s being created, and a rank.
14:32 The rank is set to be one more than the last rank currently in the listing.
bsmodals library has a way of declaring forms and using REST methods to update objects that are associated with them. In line 22, I’m using the
change_form()—which you saw, that was popped up when you hit the Add button—and I’m calling the
.show_create() method on this form.
.show_create() knows how to talk to REST using a URL, doing a
POST, creating a new object. The first parameter to this method is the URL of the
'/api/v1/entries/', because I’m creating a new entry.
15:16 The second parameter is the default data for the dialog box that’s being popped up. The dialog has two hidden fields, which are the timeline and the rank, and then the two fields that you saw—the title and the length. That populates the dialog box.
When the user hits Save, the server’s contacted by the
bsmodals library, the REST
POST is done, the object is created, and then the third parameter of
.show_create() is a callback.
The response contains the newly created object.
vm.entries.splice() is a way of taking that
response object and gluing the result into the
.entries listing on the Vue.js object.
16:01 This adds the newly created entry to the list on the screen.
edit_entry() works in a similar fashion. Once again, using the same form, this time calling a method called
.show_patch() takes the URL of the entry that was passed down as included as part of the JSON payload, the entry object itself, which it uses to populate the form, and then when the user hits Ok in the dialog box, a
PATCH is issued to the server. The REST code is triggered, the entry is updated. The third parameter to
.show_patch() is once again a callback. Once the entry has been patched, the server responds with the updated entry.
vm object has the
.entries array spliced, replacing the old entry with the new one that was just edited.
You saw on the screen that I’m using a third-party library for Vue.js called Draggable. The Draggable library comes with its own tag, and inside of that tag, I have specified that when a drag action completes, that this method
entry_moved() gets called. You’ll recall that I don’t want to just call
PATCH here on the rank number, because I need the object’s
.save() method to get called on the server.
So I’m calling a specific, custom REST action instead. I do that by using jQuery’s
.getJSON(). The URL is the
change_rank/ URL that I showed you earlier, complete with the ID of the entry that’s being changed and its new rank. And
.getJSON() has a callback,
and I take all of the entries that come back from the
change_rank/ and replace the client-side copy.
Similar to the Add button, each Delete button is also wired to a function. That’s this
delete_entry() method. Inside of that, I’m using another dialog box out of the
bsmodals library called
confirm shows a title and whatever text you give it and presents a Yes and No button.
18:14 Then it has a callback mechanism that checks whether or not the user hit Yes or No. On line 52, I look for a negative result and do nothing.
The REST call happens on line 56. If you get here, then the person hit Yes. It takes the
Entry object on the client side, grabs the URL, and does a straightforward AJAX call using HTTP
DELETE on that URL. The object has now been deleted on the server side.
18:46 There were a lot of moving pieces in that. I hope it wasn’t overwhelming. My intent was just to give you a flavor of how the REST pieces can be used inside of a single-page application.
18:57 If you’re truly interested in how all those pieces work, you’ll probably want to dig into the sample code and walk through it yourself. Next up, the last lesson—a summary of everything you’ve learned.
Thanks Shivan, appreciated!
Thank you for, another, great course Christopher. I’ve tried to rebuild the SPA application by typing along with the Python stuff. I ran into problems when using the default contents of
timelines/apps.py: it didn’t show the entries in the timelines. When I removed the line
default_auto_field = 'django.db.models.BigAutoField' it did load the entries.
It has been a while since this course came out and there are likely subtle Django things that might get in the way. I haven’t played with the problem you describe, but I do know that
BigAutoField is a more recent addition to Django. I suspect the problem you ran into is version specific.
At some point, maybe I’ll do an update for Django 4.1.
Become a Member to join the conversation.
Shivan Sivakumarnan on Sept. 3, 2021
Beautifully taught here!