Django Dynamic Forms Tutorial with Htmx

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everyone welcome to another tutorial in this one we're going to be building dynamic forms with htmx and showing how you can use htmx to achieve the same effect that you would using django form sets so if you prefer to read instead of watch the video then you can find the link to the blog post down in the description of the video and the finished code for this project is also in a github repository which is in the link in the description so with that being said we can get started with the tutorial so i'm going to be starting this project from scratch just create a virtual environment i'm going to use virtual env and then just activate it and we're going to first install django this is going to be the latest version right now which is 3.2.6 if you're watching this in the future and it's a different version then you might want to just install this version that i'm using by just putting the double equal signs and then completing the tutorial and then upgrading to the latest version after the tutorial but so once you've got that installed you can then run the django admin start project command and i'm just going to call this htmx django or django htmx and then i'll just open this in vs code right so we've got the django htmx folder here and just to get everything started first i need to reactivate the virtual environment so i'm just going to do that and then i will change it back into the htmx folder and we're going to run the manage.pi migrate command to apply the initial migrations and then i'll create a super user as well and i'm just putting in some basic password right now i can go and run the server as well just to get that running and then i will open this up here okay so that's running now the htmx documentation you can find on htmlx.org so you will want to keep this open just to read along or just to use as a reference and really you can find everything about htmx here this it's really good documentation so we'll refer to this throughout the video just to explain some concepts but okay so that's set up now i'm just going to stop the server and i'm going to run the start app command and i'm just going to call it books and we'll just use books as an example for this tutorial just because books are pretty simple and so now that we have the app i'm just going to go and put that into the settings up hi so we can go here and we'll go to the installed apps and then we'll just add the books app over there right so inside the books models.pi i'm just opening this with some shortcuts i'm going to paste in some code that is already written and it's not really related to the tutorials just to get us started so here's two models an author and a book and the idea is that an author can have as many books so we've created a foreign key from the book to the author model and it just has two fields the title and the number of pages this is just to get us started so we can use this so we have those two models and then i'm going to go into the admin.pi and i'm going to register the models in here so that we can just see how this looks in the django admin and so i'm using the tabular inline from the django admin for the book model and what this does is it allows you to specify in lines on another model admin so that you can use this as an inline form set and if we first run the migrations then we can see this in the django admin so make migrations and then migrate and then run the server and we'll go into the django admin and log in with the super user credentials all right so there we can see the authors model not the books model and that's because if we go here let's create just one author for example we could call it bob and you'll see that we get this what are called inline form sets here in the django admin so i can add books with the title and the number of pages and i can also delete them as well but i'm just going to save this without any books so we have one author being bob and this is again just so that we have something to start with before we take a look at htmx it's important to just understand how django form sets work and i'm going to run through it relatively quickly so if we come back here into our books app which is where we're going to work out of we're going to create a forms.pi file and we'll save from django import forms and then here we're gonna well also we'll need our models so i'll import the book and we're going to create a form set to create as many books for an author in exactly the same way that it functions over here so the idea would be that you can add books and then save and it saves all the books that you added at once instead of having to create one book at a time have the page reload create another book etc so we're going to first create a form for the book and this is just in my opinion a little bit better because you can customize the form in terms of styling and in terms of labels and all kinds of other information so it's just better to start off with a form so i'm going to create a model form and this is then going to require the class meta and the model is the book and then we can specify the fields which is just going to be the title and the number of pages right so that's all we need for the form now to create a form set we have a bunch of options that we can pick from so if you go from django.forms.models then you can import quite a bunch of things so if i just put command space or actually control space then i get different options here to import and you can see we get inline form set factory you've also got the model form factory and the model formset factory so these are different sort of functions or tools you can use to build form sets which are basically what we see here you take one form and you can replicate it as many times without having to write more code so i'm going to use the inline form set factory one because this creates the same effect of inline forms here but if you wanted to use any of the other ones it's also fine so just for an example i'm going to use the inline forms at factory and then i'm going to come over here and i'm going to say that the book form set equals to an inline form set factory and there's a lot of arguments that are required here so first if we can actually see all the arguments you have the parent model which is the first thing and then you have the model you have form form set so there's a lot of different arguments that you can pass in here the first is the parent model and that's the author so we need to import the author and then we can specify it here then we specify the model that we're working with as the model that we want to create as many as we want so that's that being the book so we have the book then we can specify the book form and then you can specify other things like can delete which means do you want to have the delete button here so you could do that you could set it to false if you don't want to show that and you can add things like the minimum number so the minimum number of books that need to be created so you could say at least two books and you can also specify something like extra which means how many extra forms do you want to show and for example we could set this to zero which means that we don't want any empty forms that are extra forms essentially being rendered in the form and you can play with these arguments to see what this does but basically we'll take this form set and we'll bring it into views.pi and use it here so we'll say from our models we'll import the models first so the authentic and we'll say from forms import the book form set then i'm going to create a function here which is going to be a function based view and it's going to be we'll call it create book which is just going to take the request and the primary key the primary key here is going to be used to grab the author so that we know which author to assign the book to so here we can say that the author equals to author.objects.get where the primary key is the primary key being passed in and then we can say that the form set equals to the book form set and we're basically passing in the request.post or none and so if it's a if it's a post request we're passing in that post data otherwise it's just an empty form and so at the end we would then create some context and pass in the form or you could call it form set and pass in the form set and then return a render of the request to some template so we'll just say createbook.h and then pass in the context now if it's a post request then we'll check for that here so if it's a post request then we'll check if the form is valid so we check if the form set is valid and if it is valid then what we can do is save the form set so you can say formset.save and before we call this.save we need to assign the books to an author because right now we're not actually assigning it and it requires that primary key so for all the form sets how they work is you have to pass in the instance so this instance is it's not a property of the model it's just a property of the form sets so we basically assign it to the parent model that we're creating all these books for and then once you do that then you can return either a render of the request again or you could return a redirect or whatever you want to do so we could say return redirect and we'll say the name of the view is going to be called create book so we'll do that in a second and because it requires primary key we're going to pass the primary key in here again which is basically just returning us back to this view so we can take the view and then we'll go into django hdmix urls and we're going to work with it here so we'll say from the books model or books app we're going to say from the views import create book and then we'll create a path that takes in the primary key and that primary key is the author id we're going to use that view and then we'll call it create book so at this point you can check if everything's running and if we come into the project and we go to slash number one okay so createbook.html is not created yet so i'm gonna go to settings and go down to the templates here and then i'm going to say baster templates so that it adds that as a folder and then we can create the templates folder and i'll create createbook.html and then you can bootstrap some html template here and all i'm going to do is just say create books for and we could pass in the author here as well so we'll say create books for orthodox name and in order to have that in the context then we need to pass the author in the context here as well so i'll do that as well okay so we need to add the form here so this is going to be inside a form tag and then we can just do form and we'll make sure we add csrf token and then we'll create a button so that we can submit the form and we must specify that the method is post right so that's the basics of a form or actually this is form set and so normally when you work with a normal for and so when you work with normal forms this is what you do but when you work with in this case a form set the form set has some extra properties and so what we need to do is also render out what is the management form so this we'll see what this looks like in a second but we need to do this so just saving that we can come back and refresh here and you can see we have basically two forms being rendered out here and what i'll do is put form set dot as p so that it looks a little bit better so we've got the title number of pages title number of pages and create books for bob because this is number one author number one basically and so we can try and submit this and we get some basic field validation so these are all individual forms right so i can go and test this out and we'll just say this is test book number one and we'll say 200 pages and etc and you can submit and we get redirected back here and if we were to go to the admin and go to the authors and go to this author we can see there are those books right so the books were created and that's how form sets work now just going back to this form i'm going to inspect the page just bring this up and so here inside the form you can see a whole bunch of hidden inputs and we've got the name of every input which is book set dash total forms initial forms minimum number of forms max etc and all of these are created by the management form that we output here now this information is important and so when you work with form sets when you want to make it a lot more dynamic by adding javascript so that you can press a button and it adds another row and you can press the delete button and it deletes that row it needs to change this information as well because this is telling django how many forms are being submitted and so it's it's also important and this is where it gets a bit complex when you work with form sets and you need to make things dynamic so i was playing with htmx and trying to get htmx with form sets to work together and my experience was not great it didn't seem like htmx and form sets at least when i talk about form sets i mean this api this way of building forms does not really make sense with htmx and so the answer to using htmlx and form sets is actually just don't use form sets just use forms and so that's what we're going to do i'm going to show you how you can use normal forms and you can create all the views and create a really dynamic feeling [Music] that achieves basically the exact same effect as what you get here in the admin when you need to add books like this and you can click all these buttons and delete rows but you don't need any javascript so that's where htmx comes in so if you go to docs then it tells you htmx is a library that allows you to access modern browser features directly from html rather than using javascript so for example if you click on examples and you go to click to edit right then you can see a demo at the bottom here you have a bunch of details and if you click to edit you can see it turns into a form and then you can edit these details and so if we just do this submit now you can see it returns that same response but now it's been updated and so the idea is that you get this very sort of react front-end rendered feeling but really what's happening is it's html that's being sent and then that response is just replacing the div or the initial content that was here so it creates this really dynamic feeling because you don't have to have the page reload and you only send back what's necessary to update this little piece of html here instead of the whole thing so it's a very interesting concept and it is actually really enjoyable once you've got it to work so to get it installed there are some packages that you can use to have it directly with django but it's pretty straightforward to just add it to your templates and work with it like that in the beginning so first you just need to have the script so i'm going to grab this and then we're going to go to our templates i'm going to create a base.html file because that's going to hold everything together and then i'll just create the template like this and we'll say htmx and django now there are some attributes here like hx post and hx swap and we'll get to those as we go through examples but really you just need the script to get started and then a sort of more django specific requirement is to add a script in here and i've just copied and pasted this in here but basically you have a event listener on the document body and it's listening for this event here htmx config request and then on the callback we add headers and we're adding the x csrf token header which is a django specific header and it's the csrf token context variable so what's happening here is we're basically just making sure the headers contain the token so that we can send requests and so you need to have this this is mainly for post requests for get requests you don't this isn't necessary but it's good to just add this in here and then what we can do is in the body i'll create a block which we'll call content and then end the block as well and then in create book we can remove quite a lot of stuff here and start by extending [Music] from base.html and then wrap everything inside the block content and end block here all right so now we can just come back here and make sure that that is working correctly right so it's still there and so the first thing is going to be how do we get started with htmx and the concepts that we saw in the example with what we want to achieve here where we basically want this nice dynamic feel where we can add or click a button and it adds another form and then we can populate that form click submit and it returns us the newly created book all right so the first thing is we're going to create a view that returns just this form right so it's not going to do anything else it just returns an empty form and this is nothing new it's this is all the same django stuff that you know with normal function based views so we'll create a function here which will be create book form which will take a request and really all we're doing is just rendering or returning a render of the request with a template so we'll call it we'll put inside a partials folder which is basically going to indicate that it's it's a sort of component if you want to call it that it's not a whole page it's just a partial piece of the page really and we'll just call it the book form okay so it's just a form and we'll get to it in a second so this is going to be partials book form.html and then the context will create in a second so this context is just going to be a form right only the form variable and it's going to be the book form now not the book form set the book form so this form here we can actually delete the form set if we wanted to but we'll get to it in a second so the book form just the form is what we need and it's empty so meaning it's it's not for post requests this is just for returning the in empty state of the form so we'll take that and bring it into the urls so go over here and we can start by importing like this all right and then we'll create a parthia like htmx let's do book form [Music] and then the view and then name book form okay so the name here is very important we're gonna use the name in the template and now going into the templates we're going to go to this create book dot html and we can basically get rid of everything from here or except for the button i'll just keep the button right and so what we want to do is make use of those attributes that we saw earlier over here which is this hx post and hx swap we can basically copy those attributes paste them in here and so this hx attribute tells you that it has to do with the htmx project and so the post tells you that it's going to send a post request when you click the button and you can also do get request you can do put you can do delete so there's all the different types of requests that you can send in our case we're just going to do a get request not a post request and then you get the swap attribute and again you can come here to docs you can go to the swapping section and so it says htmx offers a few different ways to swap the html returned into the dom by default the content replaces the inner html of the target element you can modify this by using the hx swap attribute with any of the following values so you can do inner html auto after begin and you can read all of these but right now it's just the outer html and so this isn't the one that we're going to go with what i'm going to do is i'm going to create a div here and i'm going to give it an id we'll say that it's the book forms div and so what we want is when we click this button it's going to add the response to this div so what we do is we use the before end property and what that does it will is say we had five forms in here already once you click it it's gonna add it to the end of this div with all the forms so it'll be the sixth element inside this div so it just it just adds it to the end and there's one more attribute that we need to get this to work because we're referencing this div we need to tell htmlx that we're not talking about this button because right now if we were to click this it would it would just look at this button but we needed to have a target and so the target which we can do hx target is basically book forms like you would do in css so we're basically referencing the div with an id of book forms or the element with an id of book forms which is this now lastly here the url needs to be replaced with the correct urls so we can add the url here and the name is going to be the name from over here which is book form so put that in there so basically when we click the button we send a get request to this url that's going to return us an html response and we're going to swap the response inside this div at the end and that's really it so we could say like add book for example so if we come and we refresh here and we click add book and let's see we're not getting a response there and it's because here i have seemed to have deleted the extends tag here let's just get this back all right refresher create the book and we can see if we click this as many times it keeps adding a form to the end of the div and like that already we have a pretty dynamic feeling that's being achieved using forms and htmx and so you can get pretty creative with how you want the responses to work and what gets replaced so for example if we submit this form we wanted to return the details of that book and so we'll go into the views first so if we come over here we're going to create a detail view and this is going to look pretty similar to this function base view here this is going to be let's just call it detail book for example and we're going to get the book by its primary key so we're going to need to pass it in over here and once we have the book we then need to create some context and pass it into the context so here's the book being passed in there and then we'll call this the book detail.html and then again bring it into the urls so we'll just do it here and we'll do another htmx view for example detail book and it can be detailed book and then we'll add a primary key in the url here right so it's book slash primary key use the detail book and then there's the url right so we've got the detail view now let's go back into the views what we can do is inside the create book function right this is the function that's going to handle creating the actual book we'll first need to change this from working with a form set to now working with the form just the book form so we can get rid of that make book form the class to use and then we don't have to call this form set we can just say form alright so if the form is valid then we will save the form and we just need to change the instance to author because now we're working with a single book form and we're assigning it to the author and once we have saved it we can then return a redirect to the detail view instead of just a form again so we can say detail book and then we can do something like this we can say book equals to form.save where commit is false and then we can just change book or form to book like that and then return what the primary key needed for this view to be the book id so we're basically returning a redirect to the detail view and that will only happen if we successfully created the book if we didn't successfully create the book then it's going to return a render of this file which is basically the entire template right that's this whole template that's being rendered but we want to return something different if there are errors in the form so we'll add an else statement here and we're going to return a render and we'll just pass in some context here with the form being the form right so this will take into account if there are errors on the form and we need to resubmit that form but we don't need to return the entire template again we can just return the partial template so this is going to be partials and then slash book form.html so let's see what this looks like in action or refresh here add a book i can add a title here and i'll put the number of pages as one let's just say we'll so we'll test this out hit submit and see we get an area here on the name of the book which needs to be detailed book not detail form and we can actually try and resubmit that right this time getting the template error so that's going to be book underscore detail.html and here we're being passed in the book as context so we can just say book.title and we'll say book.number of pages right so that's like that we can resubmit this and we get another book comma one now notice it sends us to this url and that's not exactly what we want to do when we're working with htmx we just want the html response sent back and then replace that inside the dom so let's go back to number one if we have a form here we wanted to submit and just replace the form with that book detail we just saw so the way that we can do that is if we come into the book form we can wrap this entire form inside a div and this is going to have some htmx properties the first is the x target and this is going to be this which means this whole div right this is the target to replace and the hx swap attribute is going to be outer html which basically means the whole html so it's not going to be before the end or after the end it's going to replace the entire div when we send a poster quest and it's important just to add in another attribute here which is the hx post attribute and this makes sure that the button is sending an htm x request not a normal post request if you leave this out it's going to be a normal post request so you need to add this in here and here we're going to put dot which basically means send to the current url it's the same thing as when you add the action value and you put a dot there it means the same thing it's like sending a request to the current url we can save this and what will then happen is we'll have the form display once we hit the submit button that's going to send a request to our create book view it's going to be a post request and then save the form and return an html response of the detail view which is this view here and that's only going to return a partial book detail template with the context so seeing what this does say we have a test book and you hit submit you can see testbook comma 123 is the response and no page reload no redirect it just gets replaced here and so like that you now have some htmx inside your project and you're getting a nice dynamic feeling of course we can style this later and make it look a little bit better but right now the functionality is starting to come through and you can imagine you would want to add an update button here and a delete button here as well so that's what we'll work on next so we go to the book detail view or detail template and then here we can do things like we'll create a div we'll do an h3 tag with the title and then grab that and we'll just put this in a paragraph tag and we'll say number of pages all right and so we'll then add the update button and the delete button so we'll just make two this is going to be update and delete and so the first one is going to have the hx get property and then the second one is going to have the hx post property because we're going to send a post request to delete the button and we'll add those urls in a second so if you refresh here here's another one right then you can see there update and delete alright so there's one book detail coming through and to make it easier you can also come in over here and what we'll do is we'll loop through all of the books so we'll say for book in books and then end for then we can have either the book details here or we can just include the partial template so we can say partialsbookdetail.html and it will then render this template for every book now we just need to add the books to the context so if you go to the create book view over here we're going to add books and this is just going to be we'll do it up top here it's going to be book.objects.filter where the author is this author and then we pass those books in there so if you come back and refresh then you can see all the books here and then we can play around with all of them as we go along so the delete view is probably the easiest one so we'll do that one first we'll come and basically replicate this function here and call this delete book again takes in the same arguments and we don't have to render any kind of request or html here so we basically just call book.delete and then we can return an http response so this comes from over here http.response and you don't actually have to return anything inside it just an empty response like that and then we'll bring it into the urls so bring that here we'll basically duplicate this and we'll say this is slash delete then we need to import the delete book function and use it here and then we'll say delete book right so if you go to the templates to the book detail then here we're going to send this post request to a url which is going to be the book delete right or back delete book opposite way around and this takes in the book dot id as an argument for the url so we can test this out we come over here i'll start deleting and you can see it's getting rid of the button so it's working but it's not necessarily correct and why this is is the same reason as the book form we need to add a target and the swap attribute to the entire div right so it will delete the entire div here instead of just deleting the button so if we refresh you can see those are gone now because they were deleted and now you can see it's really working and you can also double check this by going into the admin going to the author and seeing that there is only one book so that's also important just to check that so that's the delete view working next is the update view so we're going to create a view to when we send a request it will send back the form populated with that book and this is going to look very similar to the create book form here as well as this over here it's going to look very similar so we'll copy this paste this here this is going to be update book and then we'll get rid of that and like the other views we grab the book from the primary key in the url so we need to pass this in here and the book is the instance for the form so we we have to pass here instance equals book and i'm going to sign this in the beginning like that and then pass it like this right so you have basically the same thing the form being populated with the book it's inside the context and we're rendering out that book form so it's basically just going to be rendered out populated and then we'll bring it into the urls so you can do something like this you can say update to use the update book give it a name and then come into the template for the book detail and we'll add the url here it's going to be update book book.id okay refresh here if i hit update you can see it replaces the response with a populated form but if you go and submit this right and you refresh here you can see that it actually is just creating more and it's not updating that same book it's creating more of them and so to avoid this what we can do is pass in the book into the context and then inside the template you can do if book right then we're going to have one type of button otherwise we're going to have another type of button so if there's no book then we're just submitting to the current url but if there is a book then we need to submit it to the update view which is going to be another url and then just need to add the name in this this is going to be update book and it's going to take the book id right so update book we'll just double check that that's correct update book that takes the primary key which is the book id and that's going to send a post request to this view so then similarly to over here we can copy all that paste this right over here and then we'll also put in request.post or none and then the instance right so making sure that we pass in the request.post if there is a post request and then we can just say that book equals form dot save we're not updating the author anymore so we'll just do that book equals form dot save and return a redirect to the detail view so that it's the same response being returned and then likewise return a render of the form but here we're going to need to pass in the book again because otherwise we don't have that context to update the book otherwise we'll be creating books again so like that that seems to be correct and actually we're repeating ourselves in this whole block over here so we can actually remove the else statement because either way we're returning the form and rendering the same template here so we have the book form with the post request the instance updating it returning the detail view if it's successful otherwise returning the same form and making sure we have that context so coming back here say we want to update the last one i'm going to call this something else right if i hit submit you can see the title has been updated and we can also refresh the page to make sure all right i'll update the number of pages and you can see that it's returning correctly right so let's test the ad form this time so we'll do another book and we'll say number of pages 10 submit there's the book right update it and there we go i can delete it and there we go right so that's all working we have a pretty dynamic feeling now that we're achieving with htmx and this is functioning way better than what i think you would achieve with form sets and the amount of javascript that would be required to achieve it as well now the last thing is just to make it look a little bit better because it's always nice to make something that looks better and so i'm just going to use tailwind css because it's the easiest so just opening up tailwindcss.com you can go to the installation and you can go to using the cdn and then just grab the cdn link and then coming into the templates i'll paste the link here right so we have it in the base.html and you can also use the django tailwind package which is better for production usage if you want to use tailwind but for what we're doing now it's easier to test with the cdn and then you can really style this however you want so just as an example i'm going to copy over some style i've got from the blog post so i'm going to wrap some or wrap the blocks here in a div so it gives a little bit of a nice central look to everything and then if we go into the create book.html file i'm going to paste everything in here and just make sure that the names of the urls are correct all right so coming back refresh here you can see this is what it looks like if you click add form that's still working cool you can then go to the book detail and you can replace it with all of this right again you can get all of this from the blog post so you don't have to watch me type all of the html out all right again click update and that seems to work click delete and that seems to work as well okay and then lastly it's the book form we can update this as well so i'm just grabbing the html here as well replace everything there click to add a form and you can see this is what you get so let's say test all right hit submit there we go and then the last thing is really to make the forms look better so you can use the crispy forms package which is really the go-to if you want to make something look better all right so just grab that to install put install and i'll also freeze the requirements and then we'll also install the tailwind crispy forms extension or template pack which is this here it's on the django crispy forms organization and also just have to install the package all right i'll freeze that as well and then we can basically just grab all of that that's all we need to install it so going into settings then i'll actually run the server [Music] all right then we can come up here to installed apps because we need to add these two so we'll do that and then it's just the allowed template pack being tailwind and being the specified template pack so going into book form all you need to do then is just load the template which is over here or not the template the template tag so load tailwind filters and then you can do the crispy template like that so just replacing that here we'll click update and then you can get it looking like this and you've got a very nice looking dynamic set of forms and it took no javascript at all of course you can improve on this and improve on the ui and a bunch of different aspects but the main point is just showing how you can achieve a really nice dynamic functionality and just look and feel with very little code and no real extra logic outside of how you've been working with django all this time so htmx definitely has a place in this world particularly for django development i think so if you enjoyed the tutorial then consider liking the video and subscribing to the channel and other than that thanks for watching and i'll see you in the next video [Music]
Info
Channel: Matt Freire
Views: 66,709
Rating: undefined out of 5
Keywords: django htmx tutorial, django dynamic forms tutorial, django dynamic forms, dynamic forms, django formsets, django formsets tutorial, htmx forms, htmx tutorial, django, django tutorial, htmx, django forms
Id: KVq_DjIfnBo
Channel Id: undefined
Length: 48min 27sec (2907 seconds)
Published: Thu Aug 26 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.