Django and HTMX #5 - Building a Dynamic Search Component

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi there and welcome to another htmx tutorial in this tutorial we're going to extend the application that we previously built and we're going to include a very basic search component that uses hdmx to dynamically get search results and return them to the front end and we're also going to show another few features of hdmx in this tutorial this is what we want to build by the end of this tutorial so if we search for films we want to be able to get a list of films that are not already in our list so we're filtering out the films that are already in the list and we want to be able to type and have that dynamically update each time we type that's the goal and when we add a film to the list we want to see it appear here with a message at the top and as you can see the message disappears after three seconds so that's the goal let's see how to build that using and we're also going to fix a couple of issues with the application we've built so far so let's get started by fixing a few of the problems from the previous tutorials i'm in the viewers.pi file just now and what we're going to fix is this problem here if i load up the films page this should only be accessible to logged in users because the model which you can view here is linked to a user so there has to be a user for for this to work now if i log out of this page and then try and re-access this you see we're getting an error here and that's because anonymous users cannot have the attribute films in this case so what we're going to do is we're going to go back to views.pi and we're going to protect this film list i've imported a few things here that we're going to use to fix a few problems the login required mixin is a mix in that you can apply to class-based views such as the film list we can pass it in here and you can see what this is going to do is it's going to prevent users from accessing this page and it's going to throw a 404 not found so if we refresh you see page not found this can be customized even further you can redirect the user to login pages we're not going to do that but this shows you that the login required mixin is something that you can apply to a class-based view i also imported another a decorator from the django auth package and that's the login required decorator this can be applied to function based views and we're going to do it to the add film view for example because we we don't want people who are adding a film to be unauthenticated so we will use login required there and same for deleting films this is only for an individual user so we're going to add login required you must be logged in to perform this request now the delete film view also has another problem at the moment it's removing a primary key but it's not limited to just delete requests we could send a post or a put request to this endpoint and it's still going to access the function and run it so i have a final import here called require http methods this is from django's views.decorators.http module and what we can do here is we can use a decorator called require http methods and we're going to limit that to just a delete request so now only delete requests will work and that'll protect that view from being accessed by a post or a put request by accident just a bit of extra security here and it's a bit more explicit that this is only for delete requests so let's now get into the htmx portion of this tutorial so this film list is rendered in the film list partial here and you can see that we've got a list attribute with the film's name and we've got some hdmx here so we're going to extend this and i'm going to create two sections on the page so that the left hand side of the page contains the list and the right hand side contains a search component so i'm going to copy paste a few things in here um we're going to create a section for um the list and that'll encompass all of that and we're also going to create a section for the search component now what we're going to do within the section tags is we're going to load this search.html partial which i have created and it's currently empty and what we're going to do is use the include tag to do that so include and we'll include partials search.html this is going to contain the input that you're going to the user is going to type the film that they want to search for into that input we'll get to that in a second let's save this all we're doing is that we're splitting this page into two sections using flexbox and justifying the content between them that creates two sections to the page and we're now going to build out the search.html partial now i'm going to copy paste this just so we're being quick about it um it's another div but within that we have a csrf token and we have the input this is where the user will type what they want to search for let's have typed text and it has three htmx attributes um the first one determines that it's a post request and we're going to create this url in a second it's called search film the target is a dev with the id of results you can see that at the bottom this is just an empty div but what we're going to do is when we do the post request to this endpoint we're going to load the html response into this div and finally the trigger for the request is going to be the key up command when the user types we want to make the request but only after 500 milliseconds of the user not typing any more characters this can help prevent overwhelming the server you've seen that in the trigger modifiers video finally we give the input a name of search and this can be accepted on the server so let's now create this url for the search film url here what we're going to do is we're going to paste this into the hdmx url patterns it's going to have a root of search film and it's going to load a view called search film and the views.pi so within views.pi we will create that view now so it's going to be search film and it will take the request and we're going to get the search text here and it's going to be request.post that will give us the data that was sent and we'll get the search value that was sent here and you can see that that matches the name of the field that's how we get that so that is what the user has typed in we now need to return a list of the films that contain that text so what we're going to do here is we're going to create a variable called results and this is going to be equal to a database query it's going to be fill film.objects.filter and we're going to look for the name and we want to see that it contains the search text that the user has sent to the server now this will match if it's an exact match but we also want to put an i here this is a case insensitive search so this would mean taxi driver if that was entered in the front end would match it would match taxi driver and the database that's what we want we don't care about the case so we will use i contains here and that will return the results that we want and we'll set a context of results variable called results equal to that there finally we're going to render another partial here and we'll use the render method for that and the partial's called search results.html and we'll pass the context into that so this is the important part of this partial we're going to build that in a second but it contains search results in a variable that we're going to use so that's all for the view for the moment we are going to extend that in a minute but let's now go to this empty search results partial here and i'm going to paste in this again so remember we set in the view context variable called results that's what we iterate over here for film and the results we're going to show a list that's quite similar actually to what we have here but one difference is we're going to show a badge that is green and it allows the user to add the film from the search to their own list so i'll save that and now let's see if we can load that up now and we're getting some pretty ugly styling here so i'm going to fix the the width of this let's go back to the film list component currently this is column 4 because this previously this spanned the whole page let's just make it column 12 now and remember that this is actually taking up six columns so that'll span the full width of six columns now you can see that so now if we start typing a film in here let's say taxi driver you actually get a small list component here with a little button that says add and if we just type in a character like e we see we get lots of films that are in our database here now i'm going to try and add taxi driver and this is going to highlight another problem with our application nothing seems to happen here it's actually already in the list but if we look at the the terminal here we see we've got a unique constraint field and that's because we're trying to create this film called taxidriver again let's go through why that's happening the search results when you click add film it goes to the same url and this goes to here because remember when we type a film in here let's just type random data it's going to create that object in the database if we go to views.pi the add film endpoint is trying to create this object every time and something's passed to it this is not optimal because imagine you had two users who wanted to add taxi driver to the list they would both try and create that object the second person is going to get this integrity constraint failure because this should be a unique field in the model as you can see here unique equals true and the name field so the very simple remedy for this is to use get or create which is another function on django's model manager and get or create will first of all perform a lookup in the database to see does this film with that name exist and if it does it will just return it and if it does not it will create it and this returns a tuple with two objects um the first object is what we want that's the instance that's why we index in at zero the second object in that tuple is actually just a boolean that tells you whether or not the instance was created so that will solve that problem for us here so if we go back to the the template now refresh the page here and let's type in f and if we try and add fargo to the list you see that it works we get fargo and it's returning html to this list component and re-rendering now i think it's worth walking through how this is happening so let's do it right now quickly what we're doing is we perform a search which goes to the search film endpoint and this loads in the search view here at the bottom where we extract what the user has typed and we filter down the objects in the database just the films that contain that text and finally we render a search results partial so what this does whenever we type in here we're hitting this end point here which is returning this partial here search results all this is doing is that underneath the search box it's rendering a small list with all of the elements and it has an add button when the user clicks the add button you can see there's some htm x going on here as well it sends a post request to the add film endpoint that we defined a couple of videos ago and it also attaches a value this is a new htmx attribute called hx valves and it allows you to send other data with the request in this case we attach a film name and we set it equal to the film's name that we're iterating over so each film that we iterate over has a button that when we click it we send that film's name to the add film endpoint now the reason we call it film name just to quickly demonstrate this if we go back to the original films.html this is the original way that we added a film up here we had a text field where we add a name and then when we click add film that should add it to the database this had a name of film name so we're replicating that here just so that it works on the same view we only need one viewer to add a film in that case so hx valves is a new hdmx attribute and you can send any json data you want through that attribute finally the hx target tells us that we're going to render the response into the film list which you can see as this id here so that's quite complicated i think it's worth walking through that yourself to try and get a handle on how that's working but the end result is that we can type and search and we get this dynamic filtering going on and every time we search it is re-rendering the results obviously we could make that look even better using proper css and a film that had more attributes let's try and add the god file to the list but it's already in the list now this is a problem we want to filter out the films that are already in our list let's see how we can do that now we're going to modify the view so in viewers.pi we are getting the results and it's all of the films that contain the text the user has typed in we're now going to filter that down even further by excluding using the dot exclude method we're going to exclude all films that are in the user's list of films so first of all i'm going to grab all of the users films in a variable called user films that's going to be request dot user dot films dot all that will give me all of the films that i have in my list in this case it will be these ones here and when i've got that what i want to do is i want to exclude the films that are in my list so what we can see is we want to exclude all the films in the database where the name is in and we're going to use another query here user user films dot value list okay it's values list actually and what we pass in is the val the name of the field we want let's name and we'll flatten that to be true now what that's going to do is it's going to exclude all of the database objects where the name is in the values that we have already in our list so let's see what happens now if i refresh the page which you don't technically need to do but if i start typing taxi driver you see that there are no search results because we already have that in the list it's excluded by default just to show you how this works um if i go to the shell the django shell here and let's expand that and what i'm going to do is i'm going to load the film.objects.all now that gives you all the films in the database what i can do is i can get the first user so let's get the user with the name of mark it's user.objects.get equals mark so that gives us the user and we can get mark's films by saying u.films.org so let's throw that in user films and what we can do with the user films is we can get just the name attribute so if we look at user films we get these objects we only care about the names so we can say userfilms.valueslist and we can pass the name field to that and that corresponds to the model's name field that's the field that we're interested in and you can see we get a query set back with these names to the reason we use flat equals true is that that just turns it into a normal python list essentially that we can use to filter out in our query set if i start the server again go back to views.pi that's that explains what this line is doing here but we're extracting the names from the logged in users film list and we are removing them from the query set with that done we have managed to automatically extract out the films that the user has in the list from this search result so let's now try adding raging bill you see that it appears here and if i now search for raging bill we don't get any search results so the last thing i want to do in this tutorial is i want to add django messages now messages in django give you a way of displaying a message one time on a response and that allows you to see feedback on particular actions so what i'm going to do is i'm going to add to the film list some messages and what we want to do is above the list we want to show a message showing which film has been added so let's go to the film list i'm going to paste this in here and this basically comes from the django documentation we check for any messages in the context and then we iterate over them and display them now we've got that what i want to do now is we want to go to viewers.pi and it's when we add a film we want to display a message to the user so let's go to the top here and i'm going to import the messages from django.contrib and within addfilm what we want to do is we want to before we return the response we want to call messages.success that takes the request as a first parameter the second parameter is a message let's use an f string here and it says added and we're going to add the name here so added name to list of films so all this is going to do is add a message to the response only on one response here after a user has added a film so let's see what that looks like now if i refresh the page add no country for old men here and that gives you a nice wee response here where it says that we've added that to the list now one thing i'd like to do is i'd like to remove it after let's say three seconds you can actually achieve this with htmx as well what we're going to do is we're going to wire up an another hx attribute to this and we're going to delete it after three seconds so what we're going to do is we're going to add to this ual here we're going to add some htmx attributes and the first one we're going to hx get and that's going to go to a url that we'll create in a minute and this url is going to be called clear it's simply going to return an empty string so that will wire that up there and finally hx trigger what we want to do is we want to trigger this when the page loads this is another special trigger in htmlx the load trigger this is done when the page loads so when this is rendered we will trigger this particular request but we want to delay it and we want to delay it by three seconds when we add a film we will now get this message displayed here but after three seconds after the page loads it's now going to send and get request to this url so let's create that url now i'm just going to paste it in to the htmx url patterns and it's going to be going to a view called clear so i'm going to copy that into the views.pi and this is a very simple view and it's going to return an empty string at this point you might be thinking you know we have a lot of html specific views here it might be worth breaking them out into their own module for now we'll just leave everything in one file but with htmlx you do get a lot of views sometimes so it's worth considering how you want to structure your project at this point we're returning an empty response so within the film list this messages ul tag should uh should be cleared out after three seconds so let's test that out now if we refresh the page i'm going to try and add another film here let's let's say blue velvet and we get the message saying blue velvet was added to the list of films but you see after three seconds without any page refreshes or reloads or anything like that it's cleared out because of this hx get request it returns an empty string which is then swapped into the dom so again you're seeing here how you can build up all this interactive functionality with hdmx it's very flexible there's a lot you can do with it this has been quite a long video so i'm going to wrap it up now and i hope you've enjoyed if you have please like and subscribe and thank you very much for watching the code will be on github and we'll see you in the next video
Info
Channel: BugBytes
Views: 623
Rating: undefined out of 5
Keywords:
Id: e7PF0v33m5g
Channel Id: undefined
Length: 20min 31sec (1231 seconds)
Published: Sun Nov 07 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.