Django and HTMX #6 (part 2) - Building a Sortable Drag and Drop Interface

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi there and welcome to another video on django and htmx we're going to extend what we did in the last video and fix our views and we're going to add the hdmx based drag and drop functionality that integrates with the database and allows the order of a user's film list to be persisted into the database and dynamically updated on the front end by drag and drop and using htmx and a sortable library so let's get started and dive into the code so in the last section we added some drag and drop sortable.js functionality that allows us to drag films to different positions in our list and we also added some new model attributes we added a new model that encompassed the many-to-many relationship between users and films and we have a new bit of data in that table and that's the order so for every user they have a list and the films within the list have an order but we've seen that that actually breaks the front end because now certain things are just not working because they're not built to deal with the many to many field having an older column as you can see down here so let's get to work by starting to fix that now so if we cross to views dot pi i'm going to do some work in this file to try and fix some of the endpoints what we're going to do is from the models we're going to import our new user and it's going to be called user films that's the model that we just created in the last section now with that we're going to change a few of the views now firstly we've got this film list view by default the film list is returning user.films.org we're going to change that and we're going to return the many to many field instead so let's cross that out there actually we're going to get rid of all of that so we're going to return from the get query set method we're going to return userfilms.objects.filter and we'll get all user films where the user is equal to self.request.user now remember in our new many-to-many model we have a foreign key to the user table that's what we reference here userfilms.objects.filter we only want to get the logged in users list of films so that's the first change we're going to make the setting changes in the add film function this is what's called when we add a film to our list from the input box or from the search results so what we're going to do here is we're going to change this line and we're going to add a new line here which is going to be userfilms.objects.create and the film is going to be equal to the film here that we got from the database the user is going to be equal to the request.user and what we're going to do is we need to also this is the problem that we were getting in the front end here remember that we weren't setting the order so we need to actually do that when we create this user film instance we also need to set the order so what we're going to do is we're going to check to see if a given film exists with these attributes so i'm going to do an if statement here and we'll change objects.create to objects.filter if a userfilm instance exists with this film and that user if that exists or rather if that does not exist so i'm going to put a note here if we don't have a film for that user with this name what we're going to do is we're going to create that and we can pass in an order here and by for now we're just going to default that to one but what we actually want to do is create a function that will um generate the new order for us we'll come back to that later on what we're also going to do is we're going to change this line here which gets the films and we're going to put that equal to userfilms.objects.filter and again we're going to get the films that belong to the current user so we're changing the structure of what's been returned here from film objects up here to user film objects and that's going to come with some consequences for the templates as well we'll get to that in a minute here we're removing the primary key that's passed up so i'm going to change that to userfilms.org we're going to get the primary key that's passed in the url and then we're going to call delete on that instance that will remove that film user combination from the database and finally again we're going to copy this to render all of the films using userfilms.objects that filter filtering down by the user's films and finally the search film endpoint is also going to change again we change this statement here to the filter statement and what we also need to do is this is now a list of user film objects so what we now need to do is we need to actually link the film to its name as you can see here what it's doing is for each user film it's going to look for a name column but that doesn't exist in our new model it did exist before in the film model but now we're going to have to follow the foreign key from film to get the name so how do we do that in django well we specify the field which is film and then two underscores that means follow this foreign key and look for a name field so we get the film we follow the foreign key to the film object and we get the name from that so that's what we need to do there and i think that should do it it's still not going to work completely properly here but if we search and try and add an object we're going to get a new error here i'll scroll to the bottom again it's the not null constraint because we didn't actually set an order up here i don't think go to add film and we say order equals one so what we need to do now is change the way the order is actually generated so we're going to do that next but before we do that we're also going to have to change some of the template code now if you look at the film list we're iterating over the films that are passed in this used to be film objects that had a name attribute but now it's user film instances so for each film we're going to change how we get the name we're going to have to follow the form key film.film.name that's a bit confusing but what it means is we have instances of the user films which have a film attribute that we follow to get the name so that's how we do that we also and we're passing to the delete film instance the primary key that can remain the same because the delete film instance now deletes the user film object so we just pass the primary key there what we're also going to do now is generate a utility module so within the films folder i'm going to create a utils.pi and within here we're going to create a function that allows us to generate a new order when a user adds a model to the list so currently if a user was to add a model a film they need to have an order that is basically at the end so if there was already five films in the users list the next one has to be order number six so let's see how we do that now i'm going to create a function here and i'm going to copy paste the code in and if i paste that code in here just move that down so we're importing the new model user films and we're also importing a function called max from django's models module so what we're going to do this is going to take a user it's going to find all of the users existing films in their list if there are none we'll just return one because the order should be one and if the if the user's adding the first movie it should be of uh order one but films do exist in the database what we're going to do is we're going to get the current maximum using django's aggregation utilities and we're going to use the max function to go through the existing films of the user find the maximum order that they have and we'll return the maximum plus one to get the new maximum so what we're going to do is we're going to import this in the views.pi file so if i go to the top and we will see from films.utility utils import and what was the name of this function getmax order and now within our uh within our code here when we're creating a user films object we can actually just call the get max order function and pass in request.user and let's make this a bit more readable by breaking it into multiple lines so we're creating a userfilms object if one doesn't exist with the given film and the given user and we're passing a new order which will be the current maximum plus one and we also need to remove this line here where it's trying to add the film let's try removing that and now we'll save this and we'll try reloading the page if we search for some objects we now get this message displayed here and you can see that the message disappears as it did before so we're now able to add films to the list can we delete them yes we can so what we're going to do now is we're going to actually hook up htmx if i go to the film list we're trying to send a post request to an endpoint that doesn't exist so if you look at the network tab in the in the browser um let's go to the network tab when i add a film here let's just add a few films i'll add another one and if i try and drag this around you'll see that this is htmx trying to send a post request but we haven't actually created this endpoint so what i'm going to do is go to urls.pi first of all let's give this a name so we'll use the url template tag and we'll call the view i will call the url sort so we're going to create that now in urls.pi so within here i'm just going to paste this code and you can see that the end point is just sort i'll add a slash to that and we're going to now create the view sort that's going to take care of handling this for us so at the bottom of the viewers.pi we'll create a sort view which takes the request and what we're going to do is we need to accept data from sortable.js so for now i'm going to pass and if we look at the data that was sent we're going to see down at the bottom here of the network tab we're going to see that the form data comes into the server and it has an order of movies now these are actually the primary keys of the user film instances so if i change this order you should see four and three becomes three and four at the bottom because the primary keys have been switched so what what this is actually doing is from top to bottom it will send an order the primary keys of the user film instances and the reason it's doing that is because in our little hidden field here this value is what we specify um as the value that's sent to the server and it's the primary key of the user film instance that we're iterating over so we're now going to accept this on the server so let's see how to do that now in the views.pi file we've got this sort function now i'm going to paste this in here the because we're doing an htmx post request we use the request.post attribute and we're using getlist to get the attribute and the post request data of the key film order and that corresponds to the name that we're given this heading input so we get the film order and if we print that out in the back end we should be able to see it on the terminal and what we're going to do next is we're going to set up an empty list called films and this is going to be what's returned by the view here and what we'll do for now is we'll just return an http response with an empty string so we can actually view what's sent to the server here so let me add another film to my list again this is not a css tutorial so this is certainly not a responsive website but um what we're going to do now is we're going to rearrange the contents here you see that it disappears because htmlx is now swapping an empty string into the content we'll fix that in a minute but at the bottom here um we send the request now we've got this particular film order if we look at the terminal you see the order coming in as a list of stringified numbers essentially so what we're going to do is we're going to iterate over those and set the order appropriately let's see how to do that now so i'm going to create a for loop here and it's going to iterate over all of the films in our list so for film primary key and and this is going to be in that film primary keys order that we've got so for each one of these numbers we're going to store that in a film primary key variable as we iterate over what we're going to do is we're going to get the user film and that's going to be equal to userfilms.objects.get and the primary key equal to the film primary key there the film primary key and remember that's the user film's primary key here and we're going to now fetch that based on what's passed to the server in the order that it's um actually on the front end so when we get that what we can do is we need to set the userfilm.order so what we're going to do is we're going to set that to the index of iteration starting from one so there's an easy way to do that in python i'm going to create a variable called index here and we'll use the enumerate function that's built into python that's a built-in and we start at one so we're not starting at zero as we normally do because the order should really start from one so for index and the film's primary key and enumerate film primary keys order we're going to set the order to that index so in the first iteration it's going to be one second iteration will be two and that will match what's being sent here as we drag and drop these contents we're going to change what's been sent the order and in this case the primary key 5 will be first in the list we've dragged that to the top so what we're going to do is we're going to fetch that and set its order to 1 because it's the first primary key that comes in i hope that makes sense it's worth working through this yourself to see how it works once we've got that we're going to call the models save method to persist it to the database and we're going to append the changed model to this empty films list that we set up up here and if we remove the print statement i'm now going to return um the film list partial i'm just going to copy that and we've got a films context variable that we're going to set and make it equal to a films list so i'll replace the http response with this and that will now swap this into the hx target which we set to be the film list which is remember from earlier tutorials the film list is basically that encloses this entire partial here so that'll swap that in now we're going to see how that looks on the front end hopefully this will work so now i'm going to add another film let's say the godfather and if i rearrange these contents we now see that we're getting a response and things are not disappearing let's now add an indicator here to see what the actual order is this will make it a bit more clear i think so if we go to the template film list here we're displaying the film's name what i want to do is actually display the film's order as well so i'm going to paste that in there remember these user film instances they have this order attribute so that should now show us the order of the films and you can see number one escape from new york to his godfather if we drag and drop you see that actually now dynamically changes because when we finish the drag and drop action that's an hx trigger of the sortable end and that will then send the request to this view with the new ordering which we then iterate over and reset the order element or the order attribute on the user films models so once that's done we save the models and we return the rendered partial and it updates the field dynamically so this is all working quite well now um we can rearrange contents and it will dynamically update if we move god file to number four it changes to number four there's one problem if we remove an item you see that we are actually missing number two here so what we want to do is when we actually delete an item from this we want to reorder the existing elements so what i'm going to do is within our utilities i'm going to create a new function called reorder to do this so let's write a function reorder and this is going to take the user in as well again i'm going to get the user's existing films and what we're going to do now is if no films exist if not existingfilms.exists we're just going to return nothing we don't want to do anything we don't need to do any reordering if the user doesn't have any films but what i now want to do is say number of films let's get the number of films the user has and that's going to be existingfilms.com and with the number we can now generate a new ordering based on what's passed in there so the new ordering is just going to be a range statement in python and it's going to go from one to the number of films plus one so this is just a range from one up until the number of movies that are in the database for that user finally what we want to do is look over these films and the users list remember they are by default ordered by the ordering field so we'll get them in the correct order we now want to reset the order attribute from one to the number of films in the users list so if the user had six films in the list and we deleted one of them we would then have five there would be a number that has been removed and we now want to reorder the films from one to five so let's go back to our utilities file what we need to do is look over the existing films so for let's say for user film and existing films and what we need to do is set userfilm.order and we're going to set that to one of the new orderings now again we can use enumerate here um i've actually got this new ordering so let's actually use another python built in let's use zip so we're going to zip the new ordering and the existing films um so new ordering is just one to the length of the number of films in the list and the user's film we're going to set that so userfilm.order equals order and then finally of course we need to save the model userfilm.save and that should do the trick we now refresh the page obviously it's broken at the moment but if i try and change this you see that it actually correctly updates the index or the order here and let's add a bunch more random films you can see it's got the correct order it gets the maximum plus one and finally if we move pulp friction to number two you should see that that gets the order two so we actually maintain the order when we refresh this page pulp fiction is still number two and if we delete pulp fiction you should see that all the films below it have their orders increased by one but that actually didn't happen because i've not called my utility function anywhere so let me just do that now within the viewers.pi we need to import from utils uh reorder function and this is called when we delete a film so after we delete the instance we will call reorder and we need to pass in the request user so hopefully that will do the trick now um if we refresh the page let's reorder again that will fix the um the orders there if we remove mulholland drive we should now see that the orders maintained we don't have that missing gap between the films so this has been quite a dense video it's sometimes hard to explain some of this stuff but i hope you've gotten something from it and i think this final utility here is is quite cool where you can drag and drop these films and it will dynamically update the database based on where you drop them the sortable libraries used for this along with htmx so i think it's a very nice and easy technology stack and very impressed with hdmx and i'm looking forward to making more videos so do like and subscribe if you've enjoyed the video and thanks for watching and we'll see you in the next video
Info
Channel: BugBytes
Views: 557
Rating: undefined out of 5
Keywords:
Id: 5Fuwo4tVXmE
Channel Id: undefined
Length: 19min 58sec (1198 seconds)
Published: Wed Nov 10 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.