Django & HTMX Mini-Project - Building a Live Sports Polling Application #1

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi there and welcome to another bug bites tutorial in this tutorial we're going to build a live sports application where results are updated in real time on the front end of our application we're going to use django on the back end and we're going to use htmx on the front end to pull that django back end for updated results so the page we're going to build is going to look something similar to this where we have a bunch of results and these can be updated when you run a manage.pi script called result generator we're going to build that later on but for now let me show you how it will work when you run that script htmlx is going to pull the back end and this script is going to update the results you can see some of the scores are changing sometimes games are marked as full time as well we're gonna see how that works on the back end later on but the idea is we can simulate a real match day where scores are updating um in real time and you don't need to refresh the page to see that so we're going to see how to build that in this tutorial we have a starter project on github this contains a requirements file and we're using django of course and django extensions we also use django htmx when you clone this starting repository make sure you run pip install dash r requirements.txt and that will install those packages we also have a few other things set up in our settings.pi file we're using django htmx here and we're using the middleware as well within the middleware setting so django hdmx is a library that adds an hdmx attribute to your request object we're going to see more about how we're going to use that soon and we have standard things set up in a django project we have a urls.pi file that includes the app urls and we also have our static url set up so that we can reference css and javascript files if we need them and on top of that we have a score application a django app within our project and we have a templates directory set up with some simple templates we have uh the skeleton for a manage dot pi command that we're going to build that'll load teams and we also have a simple view here that renders one of those templates so that's the starter project clone that install the requirements and you should be good to go the first thing we're going to do is we're going to create the database models that we're going to use in this application so if we cross to models.pi i'm going to paste in these models here just so we can keep this quick now the models we're going to use um let me get rid of that we have a tournament model which contains the name of the tournament and it's just going to be one tournament in this application the english premier league we also have a team model which contains the name of a team within the tournament and finally we have a fixture model here now this is the important model in our application it contains a link to which tournament the fixture is in it contains foreign keys to the home and the away team and the fixture and it also contains positive small integer fields that reference how many goals the home team and the away team have scored and finally we also have a billion field that denotes whether or not the fixture is finished so that we can find out whether or not matches are completed now that's our models we also have some dunder string methods they aren't really important but there they are so the next step is we're going to make the migrations so if we run python manage dot pi make migrations that should generate these changes within a migrations file and then we can use the migrate command to reflect those changes into a database so that will create the tables for the tournament the team and the fixture entities so now that we've done that what we're going to do is we're going to populate these database tables and we're going to use a manage.pi script that we've got here called load teams now this at the moment is set up to return a not implemented error or raise that error what we're going to do is get rid of that and we're going to paste in some more code to generate these themes now i have a list of teams here that i've prepared in advance this contains all the teams in the english premier league and we can paste some more code in here and we also need to import the the models from our application so if i import the models that we've created what we're going to do and this function is we create one tournament a single tournament called premier league and then we add the teams from our teams list up here we check firstly are there any teams in the database by using the count method if there are none then what we do is create a team object for each team within this list and then we use the bulk create method to create all those themes in the database otherwise if there are already teams in the database we simply fetch them with team.objects.all and the next step is we create a bunch of fixtures from all of these themes so what we do here is we are using the python range function and we're skipping two teams each time and we get the index of iteration and we create a fixture object containing uh the team at index i and the team at index i plus one and we also attach the tournament to that fixture instance as well and what we do is we use the i plus one syntax to get the next team in the list and because we're skipping uh by two there's a step size of two in our range function that means that we don't overlap any team so the first two teams will be uh these two i will be zero and then i plus one will be one and then we skip forward two using the step size and range to get the next two teams and that's how we get no overlapping teams within these fixtures and once we've created this list of fixtures we're appending each one of these to that list we finally use the bulk create method to actually create the fixture and we also need to add a line here and that's going to be set that to team.objects.org that will refresh the teams from the database and we'll have the primary keys we need that when we run fixture.objects.bulkcreate so if i now run this command it's pythonmanage.pi loadteams you should find that that loads the teams into the database and you can verify that in the shell if you want or you can use sql lite browser so if i use shell plus and i look at team.objects.all you see we get a bunch of team instances and it's the same for fixture.objects.oh we can look at that too and we get a bunch of features back that we have created so if we now run this server we're going to see what we've got so far in this application if i refresh this here you should see we have a scores uh tab in the nav bar it doesn't currently do anything so what we want to do is build out that fixtures page and we're going to create a url and a view to do that so let's go to our urls.pi file i'm going to paste in another path here and this path links to a view called fixtures so we're going to create that in the views.pi file just now so let's define a fixtures function and it's going to take the request object and what we're going to do is we're going to fetch all the fixtures from the database fixture.objects.all and we need to import the fixture model to do this so from our models we'll import fixture and once we have that we're going to create a context dictionary that has fixtures as a key and it references all of the fixture objects and finally we return the render method and we're going to render a template that we haven't yet created and that template's going to be called fixtures.html and we'll attach our context as a third parameter here let's now create this fixture fixtures.html here and we go into the templates directory and create a new file firstly we're going to extend the base.html so extendsbase.html and that defines a block that we need to create called content and what we're going to do within that block is we're going to define the fixtures so i'm just going to render out for now the fixtures remember we're passing fixtures to our context and now let's wire up the navbar to show this page so if we go to the nav bar we're going to copy this url here and we'll paste it into the scores section here and we'll reference the url called fixtures which remember we have created here it's got that name fixtures so if we go back to the front end and refresh when we go to the scores page we're not actually seeing anything at the moment so we're going to find out why that is so the reason we're not seeing any fixtures is because i've misspelled this here it should be factuals not fixture um so that's what's referenced in the context so if we save that and run the server again we should now see hopefully that we have some fixtures on this page now currently this doesn't look anything like what we want but it is a start so what we're going to do now is we're going to style this page and we're going to use the bbc scores and fixtures section here this is the kind of look that we're going to try and replicate on our page so let's build up that page now within our fixtures.html we're going to add more code here so let's get rid of this fixtures block here i'm going to create a main tag and within that main tag we're going to we're going to try and replicate what's on the bbc page here and we're going to start by building out this header here so let's paste into that field here an h2 tag and it's going to look at the first fixture and the query set and it's going to follow the foreign key to that tournament and the name of the tournament so it's going to display the name of the tournament along with the message scores and fixtures so if we save that and refresh the page here we should see that we get premier league scores and fictions so let's split the screen so we can see live updates as we change things here and the next thing we're going to build is the search bar at the top here we're not going to use this until the next tutorial but let's build it in here and i'm going to paste this code here it's a form that wraps a text input so if i save that and we go back here and refresh you see we now get a search box at the top of the page and finally what we're going to build is this section here where we have the home team on the left along with the number of goals and the away team are on the right along with their number of goals so to do that what i'm going to do is use this code here we define a div that has an id and within that div we are including another template that we're going to create in a second it's called fixturelist.html now it's important that you define this separately because we're going to use the html within this template to return that every time htm x pulls our back end so we need a separation here so let's now create that template now within the partials directory we're going to call it fixturelist.html partials fixtureless.html and within this template we're going to use a template for loop to iterate over all of the fixtures in our context and render them out so let's paste that code in here and you see we have a surrounding div then we use a for loop to iterate over all the officials and we have on the left hand side the fixtures home team and the number of goals they scored and then we use bootstraps column six to divide this into two columns the column on the left we use the text right class to push everything to the right and the text left class on the other column to push it to the left on the right hand column we have the away goals and the away team and then underneath that we have an if a template if statement which checks whether or not the game is finished and if it is finished we have a centered bit of text that simply says ft which stands for full time so let's see how that looks on our front end if we refresh this page now we see that we get what we desire and we have a system here that has two columns using bootstrap as i said on the left we have this here and it's pushed right to the right hand side of that column which is why it's here and on the other hand we have a right hand column where everything is pushed to the left so that it aligns nicely with the other column so now that we're at this stage we're going to add some htmx attributes to this to pull the back end for updates to our fixtures now we're going to add some hdmx attributes to the fixtures.html file now they're going to be added to this div here that surrounds the partial that we're including we're going to add the hx get attribute and it's going to be we're going to use the url template tag to go to the fixtures url so the htmx request will go to the fixtures url which you can see here it's called fixtures and that is sent to this view here that we've already created we're going to change this view to deal with hdmx requests in just a second so that's the url we're going to send the request to this is the polling request to do polling we use hx trigger and there's a special event in htmlx called every and we're going to say every five seconds in this case so every five seconds we send a get request to this url that's basically what that means and finally we're going to add hx swap equals inner html this is actually the default but i'm just going to add it to be explicit so if we go to a front end and refresh we see that htmx is now sending a request every five seconds to the back end you see that ajax request being sent here now there is an issue and that's that the back end is returning the entire fixtures.html so we don't want to do that what we want to actually return is we want to return this partial the fixture list that iterates over the fixtures because we're going to update these fixtures and return new results the fresh results to the front end so we want to only return this fragment if it's an hdmx request currently what the fixtures view is doing is returning the entire fixtures.html the way to solve this is we want to say if the request originates from htmx we're going to return the fixture list and we can render that particular html file here otherwise we want to just return the normal fixtures.html so if it's an htmx request we return the partial otherwise we return the entire template and request.htm this attribute is added by the django htmx package so if we check that out in the front end now we go back to the front end and refresh the page we should no longer see that um that thing at the top where it was replicating the navbar and you should see on the network tab we should be getting requests being sent every five seconds so this is working as we expect so what we're now going to do is build a manage.pi command that will allow us to change these results as if it was coming in in real time so within our project here the scores app we have a management and a commands directory we're going to create a new command in here called result generator dot pi and i'm going to paste this code in and we'll walk through it very quickly this is not critical to the actual application but it's interesting to see what this code is doing so what i'm doing is we iterate over 10 times and we sleep for a random integer amount of seconds between one and six after sleeping we select another random integer to see how many fixtures do we want to update in this iteration and then what we do is we select a bunch of fixtures from the database where the game is not finished and then we select a random order of those fixtures and we can use order by question mark in django to get a random ordering of a query set once we have a random order we can slice into that with the random integer that we selected up here to get a random number of these fixtures between one and six and then we call two class functions through instance functions self.updatefacials is game finished update fixtures is very simple it takes in the query set it iterates over each model in the query set and it changes the home and the away goals by a random integer either one or two finally after it changes the values on the model it will use the bulk update function to save that to the database and the s game finished function well with a probability of 0.3 mark a game is being finished so for each fixture in the query set passed to the function we select a random number uniformly between 0 and 1. and if that number happens to be less than 0.3 we say that this game is finished and we save it to the database so this generator because it sleeps in between each iteration it basically simulates scores being changed somewhat at random and that is fine for the purposes of our application so let's now run this script and we need to keep the server running while we do this so on a separate terminal i'm going to run python manage dot pi the name of the manage.pi script was result generator so if we run that and go back to our front end we still see this polling going on but now we see we're actually getting updated results for example this game at the top it finished 1-1 and it's full time now other results will update at each iteration of that script and in between the iterations remember is sleeping so we don't see a constant stream of updates but we do see every time that htm x polls the back end these results are changing and by the end of that script we get some crazy results such as 14 each year between arsenal and wolves not quite so realistic but that's not the point of this tutorial so the final part of this first video what we're going to do is we're going to optimize the polling a little bit to prevent unnecessary load on the server now what i mean by that is that imagine that you have this set of fixtures and they're all finished you don't want to keep pulling the back end because nothing is going to change when the fixtures are all finished that's the final scores and we don't need to keep polling for updates polling can cause a lot of stress on your server if you do it too much so we want to avoid that wherever possible so how can we remedy this so the first step is to detect when all the fixtures are finished so we're going to go to our views.pi file here within views.pi once we fetch the fixtures here we can check if all are completed and we are going to create another variable called all completed and we're going to use the python built in all function to do this i'm going to check if f dot game finished for f and fixtures so for all of the fixtures that were pulling out the database we are going to check are all of them finished and if that evaluates to true we know that all of the games are finished and we can add this variable to our context as well so i'm just going to copy this here and we add a key called all completed that is going to be set to true or false so now that we have that in the context what we can do is within the template so within this fixtures.html template we don't want to use the htmx attributes for polling if all the games are finished so what we can do is we can set up an if statement within the template if all completed which remember is what is being passed down in the context here all completed if all games are completed i want to just render out an empty div because we don't want to do any polling so we don't need to attach the htmx attributes otherwise if all the games are not completed we do want to actually attach the htmx attributes so we'll end the if statement here so let's just walk through this quickly if all the matches are completed we just use a div and we better attach the same id here actually otherwise if all the games are not completed we want to keep doing the polling because we still might get changes so we attach these htmx attributes in that case so what happens now is if i refresh the page we get an empty results list which is not what we want and that's because i've made a mistake here the end if statement should actually come after the dev that we're declaring here uh it should be put in there so i'll just fix the indentation a bit here the reason for that is because we are just changing the div here that we're actually creating we don't need to change the include we still want to include the fixtures we just don't want to pull for updates so if i save that and we refresh the page again we get these fixtures back but because they're all completed we are no longer going to see any more requests being sent to poll for updates now there is one final issue with this what we've done now it will work if you refresh the page and all the matches are finished however if you're already on the page and then the matches become finished at some point after that because when you send up an htmlx request it doesn't return this fixtures template where we've got these changes it actually returns the fixture list which is swapped in then there is no way of re-rendering this template to stop the polling happening if that makes sense i think this is a bit clearer in the blog post so check that out if you're a bit confused but what we want to do is we want to trigger a refresh of this page whenever we detect that the last fixture is completed now we only want to do this when it's an htm x request we want to refresh the page so that this block here is actually filed and we don't render the polling attributes but we only want to do that when it's an hdmx request because if it is an htmlx request we know that we're dealing with a polling request therefore if all games are finished we should refresh the page so that we don't attach these attributes here that's a bit convoluted but let's see what i mean now if we go to views.pi what we're going to do is within the block that says ifrequest.htmx we're going to add another check so if it's an hdmx request we're going to now check if all completed is true and remember that's the lineup here and if we have all completed games within an htm x request i want to trigger a client-side refresh the page and you can do this with an htmx response header so what we want to do is we want to render this here as response so response will be equal to rendering that and finally we want to attach a particular header called hx refresh and this is an http header that's attached to the response and we set it to true and it will trigger a client-side refresh and finally we return the response from this block here now importantly if it's an hdmx request but this evaluates to false we just return the partial as normal we don't trigger a client-side refresh because we don't need to we only trigger that refresh so that this block here evaluates to true and we don't attach the htmx attributes for polling so let's now see how to do that so i'm going to bring back powershell here and we're going to run the shell plus command from django extensions and i'm going to mark one of the fixtures as being not completed so fixture dot objects dot first let's get the first fixture and we'll say f dot game finished equals false and we'll save that instance so now if we go to the front end and refresh this page you see that the first feature here is not completed and because that's the case we are going to start polling for updates again you see that these requests are coming in again on the network tab of the developer tools now what i want to show you is what happens when we make this fixture completed htm x is still going to pull the back end but when it does it will enter this if statement and it will enter this if statement as well because now all the matches are completed and it's going to render out that partial and it's going to set this http header on the response and this header will trigger a client-side refresh and you can find out more about these response headers and the htmlx documentation which you can see here i'll link this in the description now what we want to do is we want to go back to the shell and we're going to mark this game as being finished again so f dot game finished equals true when i then call f dot save here i want to quickly go back to the front end and we should see a refresh of this page now it refreshes and we no longer get the polling hopefully you've seen that it refreshed the page and we no longer get the polling request so that's quite subtle it's worth working through this example yourself to see how that works but um hx refresh is a header you can add to your response to trigger a client-side refresh if you need to and that's very useful functionality to know about now finally don't be pulling the server every 10 milliseconds or something you should only pull every let's say 30 seconds to a minute for sports score because they don't update that often there's no point in overwhelming your server with requests if nothing is going to change that often so use a sensible polling interval when you're making these requests in your own applications so that covers everything in this video in the next video we're going to build out this search bar here we're going to give users the ability to search and filter down these results we're also going to add an hx indicator element and that's going to be a spinner that shows whenever htmx is pulling the back end for updates so that gives users more feedback so they can see when results might be changing so thank you for watching if you've enjoyed this video please like and subscribe and there'll be plenty more content on django and hdmx in the near future and if you want more detailed information about this content check out the blog post which is linked in the description and the code from github will also be in the description so thanks again and we'll see you in the next video
Info
Channel: BugBytes
Views: 467
Rating: undefined out of 5
Keywords:
Id: QsGZ9361hlU
Channel Id: undefined
Length: 25min 53sec (1553 seconds)
Published: Mon Dec 06 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.