Customer Feature - Building SaaS with Python and Django #89

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi welcome to building sas with python and django my name's matt lehmann and we build django apps on this episode we're going to work on a new feature for my customer and use some django views and go over the whole process of creating new features in django so that's going to include tests and all that if you're going to enjoy that so stick around and you'll see how all that works if you like what you're watching you can give me a like or subscribe down below and that would be greatly help appreciated and i do want to thank my patreon patrons um rupert abdulaziz patrick eric and phillip uh and i appreciate your contribution um if you want to help support me financially you can go to patreon.com mb layman and sign up there so let's get into what we're going to work on this evening so you're welcome to ask me anything about python django or web development that's what i'm here for and what we're going to focus on is some new feature work that is needed by my customer who happens to be my spouse and that feature is related to backfilling dates on coursework so if you're not familiar with this stream and what we talk about it or i'm focused on a homeschool application that i'm building for my family that i'm tending to make available to more people over time but my my wife happens to be the first customer and she's kind of fleshing out the features that are needed to make this tool productive for her environment and she described to me a scenario that is a challenge that she faces with the app and i'll have to illustrate it by bringing up the application so we're going to launch the app and check it out so on the application if i go to the the week view you'll see that there are a bunch of tasks here and occasionally certain tasks don't happen on the day that they were anticipated they might happen on a different kind of day but not every course that is related to the homeschool gets completed on that day like or happens on that day some might happen once a week or something to that effect so the challenge is this the my spouse said no i have a task that is for a course that only happens one day a week but we got a little off our schedule and we want to be able to mark it as complete for a different day so if the task happened to be on a wednesday for a task for a course that only happened once a week she wanted to mark it as a thursday and the problem is is that the logic of this weak view and this daily view that we have in the app doesn't really make that work well so we need another ability to do that and we chatted over and she came to we came to a solution that she liked and that's what we're going to work on so we're going to talk about how to do that and the strategy that we're going to do is to modify one of these pages and the this page is the student's view of what their course is the work that they have left to do and um we can show the remaining work or the work that they've already completed so it's kind of a record of everything that they have to do but what my spouse is looking for is an ability to update any of these and mark them as complete at that time so what that is going to look like in practice is a new column with or a new button i should say and that button will have a calendar kind of icon by it and the calendar icon will allow her to click on that and set a date for the task that will mark it as a completed task so if she misses a day she could always go into that course view and quickly enter in the date of the task that she wants to complete so that's that's what we're doing that's the general plan we're going to do this with some test driven development it's going to require some views some stuff that doesn't exist yet in the code obviously and as well as some things with the template and we'll start probably by putting this in the template of how i want it to look um so that we at least have the feel for where this is going to go and then we will add the view that backs that up so that we can we can actually make it work so uh we're going to need an icon and i'm using the uh zonda cons icon set so if you want to use some free svg icons this is a great set it's actually i'm pretty sure it's linked to on the tailwind site so you can check that out too but zonicons is the set that i'm using and they there's this really good page that lists everything and so the only kind of calendar icon that exists is this this one right here and we're going to work with that so i have the template up over here and we're going to add our new section for this new chunk of template code and it's going to be a link but we need this svg so what i'm going to do is to start is to copy in the the button that is going to be needed um and i believe it's going to be just like this so we're going to try and duplicate or replicate as much as we can to make this as efficient as possible and then um then we'll be able to do the view that's going to back it up so i'm going to do the silliest thing i can think to do which is take this tag and just put it here as a new new thing and see how that looks i imagine even that might have um challenges that we're gonna find out so if we did it right okay it looks like this duplicated the button and as far as i can tell we still have an appropriate amount of space we haven't destroyed anything spacing wise by adding the button and there's a uniform amount of it looks a little weird right now because there's two plus buttons but um like in terms of visual layout changes there's there's not much to it yet which is good that means that the pattern that i have for this page is working what i what i was worried about may be happening is this might have this might have exceeded the width of the box for this grid and it could have had the buttons wrapping or something and and that would have been bad we would have had to adjust it but thankfully that doesn't appear to be the case so we're going to um do a split and i have these icons already downloaded in a directory so we're going to um just do the split and get the directory as is i'm going to go find that calendar svg and here it is i'm going to take it and we're going to replace or the first thing we're going to do is we can close that split and then i can put it here and that would give us the actual calendar icon the challenge is right now um well there's two icons as you can see i haven't deleted the other svg and there are some other aspects that we need to take care of the namely the fact that we don't have a title for this svg so that's a problem that's an accessibility problem that no one will know what that icon does without clicking on it and you want i want to give them some hover text that's what the svg title does and we're also missing some class attributes so we're going to take these classes here because they're going to be the same set of classes that we want and we're going to put them on this other svg and i'm going to take this title as well and put that there and then we can delete this other svg now doing that is getting closer it's still not accurate yet but we should have a calendar icon now which is good so now we've got the the feel that we want here in fact i looking at this and knowing what these are i know that the plus button is for adding a new task which is kind of the last thing that people want to think about doing when they're considering what to do with a task so this whole row represents a task it would be nice i think if i could put the calendar icon uh closer to the actual task itself and leave the plus button at the end as the action that they can take the other option well i don't know i'm looking at there's this planned completion date which is uh that's interesting this might might have some wrinkles to it um we're going to see if there are effects to what this means for the layout of the weekly and daily views as well because now if with the ability that i'm about to add i mean i guess this was probably already here in some way but it'll be much easier to complete some random task in the future which hopefully doesn't break all of my scheduling logic but it's always something that we might need to check so i'm going to switch these two icon orders we'll take this tag and we'll move it up above and so now it should be in this order here which it feels pretty good to me and right now it's going to this create a task page but that's not really what we want so we want to um what what is the actual action here so i think it's mark or really it's the set the completion date of the task and so that's the idea is like you're setting it for that particular student of this student is now done with it the other thing that we need is um there are going to be important things to consider here that we might actually want a lot of what's already here in terms of this url so this thing has previous tasks it has a next link there's just a bunch of stuff here that we might want to keep so i'm not going to delete all of that yet we now know basically our placeholder this is where we're going to click to and it will take us some place now we need to talk about what place it's going to take us to the place we need to consider is a it needs to be a form view i think i think that's going to be the right thing so let me bring up the the models for just a minute and talk about the piece of data that that the customer actually needs to fill in so we've got our models and in this setup we have tasks and the tasks are up here and the thing that connects a student i don't know i'm a little a little too zoomed in maybe i can make this slightly bigger so you can see all of it there we go so if you look on the left side over here there's this course task and if you look on the right side there's this student and you can see by tracing the diagram that they come together this coursework model coursework is the the thing that joins together a two foreign keys of the course task and the student and adds this extra bit of completed date information and that is the the item that we actually want to create or update so if someone clicks that calendar icon what they're really saying is i want to mark that my student is done with this task in other words i want to create a coursework instance to record the date that they completed the work so the the underlying thing that we're trying to build is a form view that's going to do this coursework construction and the the challenging part is it needs to be a little smart because if they click it and there's two two modes there's either the it's either like a create view or it's an update view and as the names sort of imply there could be no date associated with it because there's no record there yet because the student has not marked that as complete yet or there is a desire to change that to a date that is uh maybe it was off by one or just the the person wants to update it because they didn't actually do it on a particular day and so we need to make sure that we can account for either those they're either updating an existing record or creating a new one so that's that's the model relationship that we need to consider the challenge here is that i want to use a single um uh yes i'll be using django forms for these views that's that's correct i think what i was about to say is the challenge is that there are class-based views that make it easy to do a create view and there are also class-based views that make it easy to do an update view but blending them together gets a little bit harder i think so it might be because we have i might have to use a more generic form here so i brought the classy class base views site which is a handy reference because i think navigating the class based views documentation in django while doable is a bit all over the place and this site kind of brings all these things together so we've got these different kinds of views and i think the form view is probably the most accurate thing that we're going to get because because we're trying to do more than one thing with it so we have the scenario where we have a form that's either valid or invalid and we just i think what we're going to have happen is our template is going to we'll just have to fill in all the right details do a query and see if we can find the thing a thing and if it doesn't it's going to have to try and create a new one so i think that's going to be the challenge as we go through this process so what is this going to look like then and where is this url going to be there's there's a bunch of things to consider in this design is is what is it that we're trying where's where's the url like what's the path that we should should consider does it really matter all that much um i mean it to some degree it does but uh that's that's design consideration we'll have to think through so i want to start maybe with that url thinking and i think there are two places that we could put this we could put this with the this is all related to students or is there's another argument that says this is related to uh courses and course urls so those are the two candidate homes that i think make sense um when i think about this though i think the one that makes more sense to me is a um is in the students area and the reason for that is um the two there are two models or probably there are three models in play right we have the the course task the student themselves and then this new coursework model two out of those three live in the students app so that pushes me to think that this view belongs in this area and we also have this view for grading stuff anyway so there's already some interaction with coursework that makes me want to put it there so i think this is where i want it to go so what's this path going to look like well everything under here i'm just going to write this in this file just so you can see it is like this it's like students because that's what all of these views are routed under and then we're going to have some uuid which is the going to be the uuid of the student and then we can say i'm thinking what what makes the most sense is i think is a task or tasks or something like that and we'll we would do a uuid another uid with the course task and those two things together can give us an accurate lookup that gives us the actual coursework that we want there's going to be some tricky validation here that we don't want to create coursework for a course that a student is not in we don't want to do a lookup for a course task that belongs to another user we don't want to do the lookup that belongs that the student belongs to another school like there's all sorts of validation i think that we're going to get to as we get to this form that we'll have to think through so what is a seemingly innocuous feature in my opinion turns out to be quite a bit of views to consider in in this process so let's start with what um maybe we should start with a test what better place to start within code that proves things are working let's go into the student test views area and we i'm going to put this make this thing live right underneath the test course view so there's or the student course view so there's there is bound to be knowing my conventions a student course view test case and i'm going to take these first two tests actually this is another it's a reminder to me to test against another another user so that's good um so let's take the first three tests and then we'll go after this test case class which is much longer than i realized and we're going to put this new test case i'm going to rename it and i'm going to say this is the student course task view um no that's what is this really this is the the thing we're trying to represent is really the coursework view so let's call it that because that's what we're dealing with that's the underlying model now the url won't show anything about course work but i think let's let's work with the thing that we actually have to consider so we haven't this unauthenticated view and we're going to need a course task and we're going to say we'll call this the students coursework view and we'll pass it in and the parameters we want it to have are going to be the course task uuid and the course task and i think this is the basic form of this but this isn't going to pass yet because this view doesn't exist yet so we're going to have to dig into this and start fixing things i think even that is enough to not even let's ignore the other tests yet because we're not ready for them but we can we can work from this so let's put in our url path and we're just going to keep doing error driven development as i sometimes call it because that's exactly what we're doing is working through this and following the errors and this might actually even pass no it won't but we'll see why and the reason it doesn't pass is kind of a hard to read error messages because this is looking for course uuid and in the test that we actually we gave it a course task uid so we need to correct this uh name parameter here that is course task uid and now the test passes but that's only because we have an existing view i'm going to change this to the well best name that i think we have for is a coursework view um more accurately it's a coursework form view because as i said we're not going this isn't a create view it's not an update view it will have to be in between those use either and to match conventions on the naming i i just usually use the name of the class plus test at the front so we'll change that as well to get test coursework form view and you can note probably immediately that this is now complaining that this form view doesn't exist which makes perfect sense i've not written that code yet so we can now come into the and find the student course view and we want i'm going to just steal this whole view whoops is that the student course view nope that's the student create view so we've got the student course view which has way more code than i want to steal um all right well let's just templates fine for now let's come in after this all this stuff and put it in here and this is the coursework form view that's what i just called it right okay so now our test should still pass why is it passing it's passing because this test is all about testing if the login required mixin is there it's checking authentication as as the test name implies so what the difference is when we get down to this lower area this is where it's going to not go so well so we need i'm going to take the course task that's up here and we're going to supply the course that is above to this course and we're going to rename this thing to coursework and name this as course task and this as course task now we've got this view this might pass as well this is a get um oh it's probably um it's failing in a template because the template is the template's trying to use the the course view and we're not providing at the right context which the course viewed that template depends upon so that's that's actually good that this needs to be changed so let's look at the template so let's go to the students course template and here it is that was a bad split i apologize um and notice when we have some of the some of these in some of these cases i do use a convention where there is a create view and an update view they happen to use the same template but in other circumstances i've been able to separate those two because there's a very clear create action that the user wants to do and a very clear update action um in this scenario we don't have that same kind of thing but we can definitely follow the same pattern of calling it the model name followed by form as we do see here with this enrollment form so i'm going to take this enrollment form because it already has probably well maybe that is there a better one um yeah it's probably the best we got for now i'm just going to take this as this as our starting place and we'll create a new file and we're going to call it the coursework form and paste in some content now this content is really wrong as you can see but um it should at least allow us to get a passing mark from the test if it's not depending on some context that we didn't pass in so let's try it again now okay so this even this one is depending on some data that we don't have and and again i'm i kind of i'm using a different file so it's not surprising uh that it's depending on something so probably what's happening here there's a url yeah and it's passing in a school year but this school year was a none variable so this doesn't exist which is why it's failing um so we can for the moment just comment this out i think i just want to get to a passing spot so we can see it pass and then poke around connect it to the template that we started with and and then have something to go to okay so we've got a passing test there and we now have the ability to go to our course template and come into this area this first link and change this we're going to change it to students coursework and it's going to take the we should have all the bits of data that we need here um can't this is too small for me to see let's get rid of the test for a minute it's a challenge with streaming is i don't get enough screen real estate but that's the reality so it looks like student is already in the context and then we have this task item thing so we need to pass in the student uuid and we need to pass in the course task uuid of [Music] course the task item there it is task item course task uuid all right that should be enough we should be able to come back to our view and refresh it and now we're if you can look at the url it's kind of hard to read because there's a bunch of extra stuff in there but we can see that it has the right general form at the bottom it says students then along the uid and then tasks and then along uuid and then there's a bunch of other details and as you can see um it says set the completion date of the task and i don't actually like that i'm going to say completed date because that's what the actual field name is and that's going to um be a better message it's like saying when did you actually do it not when is it going to be done so i like that better so let's hover over it again set the completed date of the task so that's telling them what they need to do now we can click on this and it's going to take us to an enrollment form which is going to look way wrong because this is completely irrelevant to this task we haven't looked up any of the right data but we're getting closer i think what that means though is that for now this is the this this information is correct for um what am i trying to say it's correct in terms of the url and i think it is going to also benefit from having some of these extra parameters in here to put us the right spot assuming we handle next correctly so there's another requirement probably for our view yeah all right let's return to the tests and what do we need to do next what what other tests do we have here let's see we want to check that we want to make sure that there are certain 404 cases so there's some error cases that we need to check too and let's let's talk about actually maybe we should just back up and talk about the process um i want to make it so that you can get all the right data first so if it if it is the a valid student in other words the student belongs to the user then that should be okay if it is a valid task then that should be okay we also need to guard and make sure that the student and the task would happen to belong together which is a more complicated check but we can probably do that in the form and and maybe we can even defer that so that there's not a problem until somebody tries to submit that form because it's going to be a painful thing to check earlier than that so we want to check that those two things are those two primary conditions are true that there's a student and there's a task and they both happen to belong to the the user in some way and if they don't then they get a 404 so this last test is actually kind of correct but really i think it's probably better to just re retake take this test and redo it with a slightly different data so i'm going to take this and i'm going to say um the the part that we do want to get is the response and we don't want to do a get check 200 because we don't definitely want to check for 400 so or excuse me a 404 response so we're going to paste that there and then i'm going to delete this test because ever everything else about it is not what we need and we want to say other user student and a student belonging to another user is a 404. um okay why um this is just good sanitary it's just like protection to make sure that people can't access other people's stuff um there are probably not going to be many people that would want to even try but you know people write scripts right bots they try to find exploits and sites and yeah so that that is one one facet um code alite is that how you say it codial codiolite could code code alite i don't know i don't know where the where to break that but um so yeah that what you're suggesting in your question there is one of the checks that we'll put in the form to um to make sure that the student and the task go together but this one is more basic of in in the actual model diagram which i'll open up again just because it's useful as a visual reference when we have a student where is the student the student's way over here if you look at the student model i don't know how well you can all see that only zoom in there is a foreign key to the school and the school is owned by an admin which is a user account which is own you know is a user so um that that is what i want to check that that student um is actually you know somebody that is associated with that user account so that's one check that's what actually what this test is about so we want to in order to prove this the simplest way to do it is we want to delete this association right here i'm just going to take that out and so now the student that's generated the factory is going to create a different user that is with it so it's not shouldn't be the right thing but this test at the moment is going to actually pass because we're not actually checking for this data we're not doing any lookups on this kind of all stuff suspect it will pass yeah all right i meant sorry i meant fail i said i totally said the backwards thing um so it's doing a 200 when it should be doing a 404. so now we have the test now we can go back to our view and beef it up to have the right stuff um so the way this is going the starting point is we want we're going to want the student to be in the context because we need to set the form correctly um and it might not even be visible well yeah we'll probably display it somewhere it'll say like set something about complete or i'll probably connect them together the the task and the student somehow in the title to say what to do so this is what we we need to return the context and that'll still fail as i would expect but now we can do a little bit more we can create we can try and fetch the student so the student is the is the uuid so we need to fetch the student and we want to say student dot objects.filter and it's going to accept a uuid and the uuid is going to come from self keyword args and we'll pull out uuid and i have confidence that those will be in the keyword arguments because um it's we've already connected the url properly to name the parameters so i can do that without kind of worrying that i'm going to get a key key error in this regard so if i did it this way actually instead of a filter we want more than that we want a get no no no that's not what we want there's another there's a helper method called get get object or 404 this is what we really want so let's come back to this area i kind of goof this up get object or 404 and you pass it the model type and then as a set of keyword arguments you pass up the extra data so this now says look at the student table and find the object the student record and if you can't find it return a 404 um and i want to add the student to the context so we'll do this and it should make my editors stop being angry at me too all right but still this is broken right now i haven't i'll show you why we'll run the test and it's still passing it's still doing a 200. why that's because i haven't added the right filtering so the other thing that we need to do is we need to make sure that we don't allow filtering just any student which is the case right now we're grabbing any student that matches that uuid but we want to make sure that that student is also part of the school so we take the school and we take the we go to the we can do it this way we can say admin and we take the self user so that's going to take the user attribute from this view the authenticated user and do a query on it now i would expect that the test would pass because now we're filtering and saying like you have to be part that student has to be part of the school from the user in order for this to work ooh um course form view course workform view has no attribute oh oh oh i'm i'm it's been a while since i've written a view so this is actually hanging off the request object so the the class view does have a request attached to it and the user is attached to the request so that now passes and while we're at it since i know i'm going to want to have the student in the context i find it helpful to go back and say in the the basic get test that's what this first test is this is a sanity checkup is everything okay and so this is a good spot for me to assert on what's in the context so we'll say self get context and we'll look for the student and that's for that second that third line that i did there that's the assigning the student variable to the into the context dictionary and i like to arrange it as the actual and followed by the expected value although there's nothing that says you have to do that and that test passed too so it means that the the data is in the context as this line right here indicates so now we have a student to work with that's good we have the confidence that that student is also protected so that's what this test is for i think the next test we want to do is essentially redo take this again as the starting point or now is where we could do this a number of ways let's try take this one because it's a little bit closer in form and we want to check that um so this test is this one's new going to be called other user task so a task belonging to another user is a 404 and we do want to use the same the student that passes i don't want to mix them up and get the wrong 404 so um hopefully let's run this test to make sure it's failing it should be at 200. yep and it's returning 404 or excuse me it's returning to 200 and it's expecting a 404 and we want to now change it so that it's just some random task so let's delete that in fact because we're deleting that we don't actually even need this we're just having some random task somewhere and that's still going to fail because we haven't actually fetched out that chunk of code yet so let's get that now but now that we have the student we can also do um we can get the course task and we're going to do basically the same pattern of we're going to do a get object or 404 we're going to say course task and we're going to do it's going to be a little bit more complicated because of the relationship and the modeling we'll work with it but the first step is to do uuid and self keyword args on this time it's the course task uuid we have to be a bit more specific because of how i named it in the url and i'll say context of course task because there's another thing that we're going to need in the actual template is a course task and it looks like we don't have the course task available in here so we will need to import that so let's go do that so coming up to here we're going to add the course task model this view should still fail because i haven't done the proper filtering so it's still passing and getting anything we what we really need to do is um this is where the association is a little harder there's a many-to-many relationship in this mix that makes it so that we can't do a direct look up on this we have to get out the set of um grade levels it's a little bit more complicated so um just kind of trust me that this is where the my modeling knowledge happens to of this particular system happens to come into play so we're going to have to look up the grade levels that belong to this user and check if this course tasks course is in one of those grade levels and if that didn't make much sense don't don't worry too much about it so start with grade levels and we might even just need um grade level well let's keep it simpler it doesn't doesn't really matter i don't i don't want i think we don't need to over optimize this so we're going to say grade level and objects filter and now the grade level has as you can see from my vim editor there it's got a school year parameter so we can say school year school admin is equal to self request user it's going to give us all of the grade levels and then we will take the grade levels and we'll come down to this course task filter and we're going to say course grade levels in this one right here that should be it so it's a more complicated filter it's a little bit annoying that's just how the nature of sometimes the modeling for what the customer needs is something more difficult to work with um but that's that's the reality of it now this test should pass because this filter is now in place and this course task is the factory is going to create a course which is going to create a grade level which is going to create a different like a whole different tree of of stuff and it's not going to match so when we when we get uh look at this line right here that filter is not going to match anything and it's going to throw a 404. as we expected all right but the get should still pass because it's still wired together because the in this scenario this course task belongs to this course which belongs to this grade level you kind of trace it up like a tree so again if it's your first time watching this stream it's uh the task is sort of the bottom or one of the bottom things and then course and then grade level and then school year and then school and then finally all the way up to the top of the the person that owns the school so the other thing we want to do is we want to assert that the course task is in the context and it's the course task from above all right great so what do we have we have um we have the data we have not set up the template at all really at this point um but that's okay but we have everything we need and we've done the additional checking so i feel like we have adequate coverage so far now here's where we need to do more and here's where it's going to get more complicated this is going to become a form view and coming back over here form view is going to require a handful of properties and we're going to need to feed the form view some initial data the form doesn't exist yet so we might have to like detour and create the form first because if i try and create a form view well let me just try it let me show you what that looks like so we're going to take this template view and we're going to change this to a form view and the template the form view derives from a template view or it's got the template response mix in so this template name is still correct but that's this is the form view that we're going to need and we need to import that so i believe that's in the generic category we can import it here i think that will be right and now if we return to the test and try and run this test we're going to get a failure and the failure is um really unclear wow um that's too bad it should it should be better than this i think in the past it has been you can still kind of see it but the problem is here that there is no form class so a lot of times django is pretty good about actually giving the message and i'm surprised that it gave this kind of um this error this very generic nun type error but you can look at this and you can you can see this chunk of code and see where it's coming from the form class is none and so it's trying to instantiate a none which doesn't work and that's because i haven't supplied any form class so we need to in order to make this work we need a form class that has a class level attribute but what goes here that's the problem is we don't have anything that goes here yet so i'm going to leave this as none and mark this as to do and say insert form class so we need to take a detour and go and down a level and create a form so we need as my view name hopefully clearly implies we need a coursework form what is the coursework form going to take as attributes it's going to take um it's going to need as it's doing verification it will need the student and it will need the task i'm trying to think if it will also need the user i don't think so because i think from if we already have the student we can do the lookup of the user if we need to um we'll see for the for the time being i'm going to assume that we only need those two things and yeah we're gonna need more we're gonna need more but we'll we'll work with this for now and if you've got any questions um please feel free to ask as again this stuff gets a little um a little goofy would it be the same validation as the test um well no it's sort of it could it could be we could push this validation down into the form that i've done on this get object or 404 my general feeling is that that views the view layer in django it's where you want to deal with http status codes the reason for that is if you try and if you try and use for instance get object 404 deep in your code then there's going to be this future where well i can't guarantee this but there's a likely future where you depend on some code and for whatever reason your business logic raises a 404 in a context that doesn't matter here's a good scenario maybe i make a form that does a bunch of stuff and it's really useful and then i have to do some background processing like i create either start celery or some kind of other background task management system and i want to reuse a form that's similar but i happen to have put in this get object or 404 well if there's a failure to look up the object it's going to raise an http 404 in a non-web context which is really confusing so i think for me what that means is like leave the http handling at the view layer and so 404 raising is here and then i'm going to pass this stuff into the form and assume that let the the form have the knowledge that this has already been validated and have real records that are matter but your question about the same validation you asked earlier um if the where was it you asked if the i think if the task is belonging to another student and and this is where the form can do that checking of you know where the the coursework form is about creating or updating an instance of a student and task combination and what we do need to check in the clean method is that those two things are allowed to be coupled because if the student is not actually enrolled in a course that has that task then we want to raise an error there so hopefully that that clears it up for you uh code codalite um that's what i'm going to call you i'm sorry if i'm butchering your handle um okay so i think we're ready to go to the form means uh we have to write some tests for that as well and um yeah that seems like a good good enough place isn't it to keep going and i think what that's going to do i guess i'll add is that this is going to clean this up later like this this context data is it's kind of big for for what you want in a context data um there's a lot of like filtering that's happening here and like it just gets a little sloppy a little messy um and i think as we build the form um this is gonna get it's gonna get tricky as actually now that i think on this more no no no no i'm overthinking it hold on sorry i apologize i'm getting ahead of myself so one thing at a time let's work on the form so we have a forms file right now there's just this enrollment form in here and we need what do we need we need we need a coursework form so it's going i'm going to put it right here above the enrollment form so let's also while we're here open up this other file and so we want a new test case and we're going to call this the coursework form and what is our first test going to be um how about a happy path test that validates so we'll say def test is valid and we will what we want to do is we want to check that um the coursework validates um i don't know that's kind of a crappy dog string we'll maybe work on that later so um none of this stuff exists yet so let's just pretend that it does and see the like optimal test um see if that that's feels right or not and this this is actually causing me to like rethink so if we use a model form which is i think what i want to do the model form gets some parameters and it gets those parameters from [Music] a variety of places yeah this is going to get interesting so let's just start we want our form it's going to be a coursework form we want to say is this valid and we want to call form is valid and assert that it is valid seems easy so far right this is not meant to be difficult at this stage so the thing that we want is it's missing a coursework form so we need to come into the forms and we need to import it so now we have this but my editor notices uh wait a minute you don't actually have that form so we want to have this and have a coursework form but we don't want the enrollment we want the coursework that's what we want this to be based off of the other thing that we are wanting in this like when we're building of course work there's not not many fields on it um let's bring up the coursework model so you can see it it's just a handful we have a couple foreign keys and a completed date field and we need all three in order for this to be successful so we need the student we need the course task and we want the completed date those are the three fields that are going to go into our form so we also need to import the coursework model to make the form all hang together if i run this now it's still not going to pass okay the reason for that is um well it's not a it's a it's what's considered an unbound form so when form validation runs it's trying to validate against something right it has to validate against actual data and we haven't provided any data yet so it doesn't know what this is and so minimally we'll have to provide a dictionary of data and bind that and binding is just a fancy way of saying setting it in the constructor to a data attribute and now by doing this even though i don't have any type information here just get this to shut up for a minute um we now have a bound form it's still going to be wrong though and this is where we're going to want to look at we'll probably just do it this way this might be easiest to print out the form errors so the printing out of the form errors is telling us what we're missing okay we're missing the student we're missing the course task we're missing the completed date we're missing all of them um no surprise i didn't actually pass them in so we need in this data dictionary we need to add those data attributes and so the course form the model form is going to expect some primary keys um how do errors come in html that's a good question the errors are in html because that is the default printout for the error so um when you when i printed form errors it calls the um what method does it calls this excuse me the stir method um hey welcome glad you're back um and so the i don't i still think i can't say your handle scalia leo scalia you all have difficult handles for me to say um so code code alite this is uh when you have a stir method like it's wanting to print out the html and the reason that django wants to do that is because django kind of expects that in your template you want to do the simplest thing possible and just say form and it's trying to dump out like let you be as quick as possible of just saying here's my uh table of information for my form and say cello and then celio maybe i should write it down and practice it some other time um so the the point is is uh we uh we need to add all these attributes in and and so the sorry i'm getting a little off track i apologize the um the html is there just to make it convenient to do template rendering um when i print it in my test that's why you're seeing it that way uh it's because i it's call in the process of calling the print on it it's running through the stir method which happens to go through that path of saying oh you must want this to be rendered out which and it defaults to that html version hopefully that answers your question codalite okay so we need in here is the stir of a student id and we need the course task um uh and that's going to be the string representation of the course task id and if you're being observant that none of these things exist yet that's okay and then we need a date and so we'll need a string and we'll put in today's date of 2101 27 and that should be enough but except i can't make a dictionary apparently we need completed date as the last key and now my editor is rightfully complaining and says wait a minute you don't have um you don't have a student yet so what what's up man what are you doing uh so let's let's actually let's build this appropriately so that we don't get bit in the future and have to come back and fix all this junk we're going to need a user because our clean method is going to want to validate that in fact we probably just want all of this stuff because we need a user we need a student and a course task that are both connected to the same user and so it has to go through this tree to get it all right so that just is the unfortunate part of the structure it requires more factories than i like um yeah that's just what we have to do so the students factories is going to be the place for two of these factories so we want the um student factory that handles that and then we need to bring in from homeschool courses tests factories import the [Music] course factory and the course task factory okay great so what do we have now we have valid data actually i think this test might pass hey it passes and maybe you didn't see it so i'll do it again um this is why writing form tests are cool they're faster they're not a ton faster well they're they're maybe a ton faster than that those view tests we're writing so the view tests execute quite a lot of code it's executing through the url routing system getting the view code getting the view running through the view um doing a template render all of that stuff and so the timing and this is reading back as 0.39 seconds and this test was not that oh well it's still failing right now but um it was something when it was passing of like 0.7.8 it might seem insignificant but when you have a larger and larger test suite those you those chunks of time half a second here half a second there those start adding up and so that's really a good advantage to writing form test if you can get write your tests closer to where um they're running so they execute less code then your test suite ends up being faster so just a hot tip there for if you want a fast test suite so now we have this path that is the happy path through this validation um and so that's that's great that's what we want um now we're ready to move on to the next piece of this or maybe maybe before we do that maybe we should hook in the form into the view that's probably the right call so let's return to our views and we'll go down here and we'll add the coursework form and we'll come down to this well where to go coursework form and change it to have coursework form okay cool let's return to our test view i think this might still fail maybe not i don't know nope past all right there's there's your time example so took point seven one versus point three nine um three tenths of a second but again those add up if you're running a bunch of tests so what do we have to do next let's maybe pause so i can give myself an opportunity to think for a moment ultimately if there because remember there are two modes uh that someone coming to this link and for those of you joining late like the context of what the heck am i doing is um let me launch the the app and so you can see it my spouse was asking for a place to add coursework add calendar dates and i'm on this page that shows the student and all of their stuff and she wants the ability to go into any of these things and click on an icon in this case this little calendar icon that lets her add in the date for that she completed a task at any time whether it's on a schedule or not so that's that's what we're doing if if you missed it so this is the view that we're trying to fill out we're copying from this other form and so we have the data now um and we have the form now but there's still more to do and i'm wondering like the the two different of the two different modes the create path and the update path what what needs to go into that uh in order to make that uh make it all to come together but i think before we do more work on the view perhaps it makes sense to do the bit of extra validation that i think will be necessary i mentioned that um we're gonna have to check that the student and the task are even allowed to go together so that's the extra test we want to write we'll say student can create coursework and the student is enrolled in a course that contains the task that's the condition that we want to check so we're have to like walk backwards through the path and check that the yeah check that those things match up and this is also bringing in some other interesting challenges of like earlier i had said that we were getting the student and the course task in the context but take a look at this like when when we are doing when we're passing in the data we're doing it from a lookup on on this though so the the model view wants to get this data and say like okay the student exists because i was able to look it up from the id but the the form view is going to be providing it from a uuid so i'm honestly blanking out a little bit on what we need to pass in is it a keyword argument i think there might be keyword arguments that go into this or form arguments i'm i'm drawing a blank we'll have to every time i come back to form views i i sort of forget some of the parameters so we'll have to play around and see what it is because i think this is the right thing in order to like get out this stuff so i can use this in the context and render it properly but in terms of building the form and doing proper validation and providing this data ahead of time we have a there's going to be different strategies on whether it comes from the data that's posted by the user or or the data from the url yeah yeah that's actually that's really interesting okay i'm again getting ahead of myself i apologize so um we want to test we know that this path is valid um good okay yeah why are you ids in the models it's a security issue um by using uuids for and really like i think it's fine to use you ids in the regular ids regular primary key ids in the models i think that's totally okay where uuids are strong is if you have that in your urls so imagine imagine i had this so that instead of these long uuids i had students well that was a uid there something like this students won this if you do this you actually open yourself up to something called an enumeration attack which means that if somebody figures out that you're looking up information about some kind of data in your system and you do it based on the primary key then they can very quickly figure out a lot of data about you potentially um especially if you don't authenticate these properly like if it if it was just like anybody could get into this and just look at the data then somebody with a pretty stupid python script could say students 1 students 2 students 3 students 4 students 5 and pull all of your data out of your database just by enumerating over that until it got to the end and said you know 404 there's nothing left so they could figure out exactly how many of something they had in there they could figure out data about it if it wasn't protected very well in fact i believe this is what happened the social media website parlor i'm not going to get into politics of all that stuff but parlor itself when there was this big data breach it's kind of what happened to them is i believe that they had a bunch of their apis kind of open with using this primary key approach and it was it's basically taking advantage of this thing called an enumeration attack and it's called that because you enumerate through the numbers uh just to figure it out so by having uuids that becomes pretty much impossible there's no way to guess what the next one is going to be yeah glad you appreciate the explainer so that's why i use them as a as a safety measure in the system okay so we want to our test here to come back and i want to test this is the actually the negative case so i want to test that um we've already test got a check for well no that's in the view so we want to check that this uses student and is part of this course in fact i think this would when i'm finally done this is going to fail this this first test is going to fail we'll see why so let's take all the same data because it's a good starting point and we'll copy it into the new test and this time we want to assert that this is not valid okay the other thing that's missing is we want to do base this off of an enrollment so the enrollment is that the student and the student is enrolled in the grade level so that's what else is missing so we want an enrollment to connect the two so we'll do an enrollment factory and it's going to take student equal student and grade level equal gradable enrollment is just a model record that connects the dots between those two so now that the student is enrolled in the grade level and the course is part of the grade level then those things come together and that shows that everything's wired appropriately in fact this is what we want actually we want to put up here for the happy path test and we want to have removed we explicitly want to break that connection down here and so we want this test to fail but it's not going to fail right now it's going to be true because we haven't put any clean method in there so now it's time to come back to where come back to the form and now we want a clean method and we'll say take this stuff i'll call the super clean on it just sounds like a cleaning product super clean um the reason you call clean on it is to make sure that the clean data is populated i'm pretty sure and we want out of here the student so self clean data get student and we also want the course task okay there are a couple of weird scenarios that can happen here both of these could be none um trying to think if i want to even get down to that level on this stream this evening because it's sort of boring it's just like you can pass in a bad uuid and it could potentially fail here i don't know this is worth really protecting because i'm going to pull this from the url eventually i don't know it's just getting i'll put a note and then we'll just leave it i'll ignore it for now or i'll ignore it on stream and you guys don't have to worry about it but i'll fix it up later it's not very exciting stuff so um just know that when you're dealing with clean methods there are cases where the form in its collection of those model instances could come back as um none and that's not good so i do at least want to put these in a gar that says that both of these things are truthy which means that they are actual records and because if we try and do operations on these and they don't meet those conditions then that's not so good um and for the moment we'll just say pass so we now have a clean and i don't does the clean have to return anything return the clean data i think um yeah sure i don't remember if that's actually part of the api or not but i'll follow suit with what's down below okay now we need to write the test that i was talking about so we we need to check that this student and this course task are allowed to be connected and if not we want to raise a validation error so what does that look like we want to check what the go up so we could get the the student has a school attached to it look at the student models there's a should be a school property attached to the student itself oh my word too much stuff in here yeah there's a foreign key to the school and we haven't actually fetched that at this point so but we do have the actual key so the i guess we want to check if there's an existence relation okay i think i got it what's this going to look like so we want it's going to come back to the grade levels again so we'll say grade levels it's a little bit similar to the code that's in the view just feels a bit repetitive but i don't know i haven't quite managed there's probably some abstraction that i'm missing to kind of wrap this up that i'm struggling to i'm struggling with and sometimes the struggle is what you want to be there until you get the right thing in your head for how do i model this appropriately and i'm getting tired of doing these intermediate grade level checks but i'm not sure what to replace it with um so i'll get there eventually so we'll say um for now we'll we'll do the same kind of filtering that we have been doing elsewhere and we're going to look for a grade level is connected to a school year as i've mentioned and so we've got the school and we can say student school id notice the id part um if you just say dot school what that you're gonna cause the the orm to do is a a look up at that moment because it assumes you actually want the school record in this case i don't care about the school record i don't actually i don't want anything from it i just want the identifier that the fact that it exists because i want any grade levels that are part of that school so i don't need the school itself i just need which school and so if you use the underscore id attribute you bypass the orm and you say give me the numeric value the foreign key and and that should be enough so by saying doing this kind of filter here that's going to give me the the right data let's go ahead and bring in the grade level as well that's in the schools app so from homeschool schools models import grade level okay so we've got the grade levels that this belongs to and we want to now check um if the task is is correct so is task in um this isn't i'm i'm doing this wrong i can already see it it's it's even more more tricky than that i'm thinking here and i think i might be going about it backwards let me back up i remember what i said the enrollment is actually the connector between the um the grade level and the student so i think what i really want is i i don't want all the grade levels that could ever be part of the school so this is not the right code i want to take the enrollment and i've already got that in model imported which is probably telling me it's probably a good signal that i'm doing the right thing so let's first get the enrollment for the student so um but which enrollment ooh this gets even trickier what are we really checking we have to bring together two pieces of information do the course task and the student go together so what we're ah i got it all right finally got it we want to find out if there is an enrollment such that the student and the course tasks grade level exist that's that's what it is so the final answer is going to look like some kind of enrollment objects filter exist check that's that's what we care about um and it's like is valid task is is what i'll probably call the variable um so we want to pass in what's the what's the first thing the easy one's the student we know that we have a student already that's going to be part of this the other thing we want is the grade level for the course task so we need to find we need to look look backwards from the course task and say what grade levels are contain the course gosh that i don't know why this i'm having a hard time wrap my brain around this particular problem so grade levels i think that's the thing that we need needs to come out and how do i do that reverse query or it's a many-to-many field okay let's go get let's go look at some other stuff because i'm not remembering everything appropriately let's go to the course task so the course task has a foreign key up to the course that's that's fine so let's look at the course the course um has a many-to-many field to the grade levels so maybe ah i think i'm overthinking this i think it's going to look like this we want the enrollment where the grade levels is equal to the course task course grade levels dot all actually it's not just equal to we want it in so if there's an enrollment that has the student and has the grade level in that collection then it's a valid task hopefully that makes sense i know this is very specific to this app it's not super like django e but it's this is the the logic that i'm trying to apply and so what was this this was a um i'm taking the instance i'm going to the foreign key i'm looking up the course um which means i'm probably going to do more lookups than i want but that's okay it's a one or two extra queries is fine there's a many-to-many field on that course record so this is a many-to-many related manager and that is why we have to call all on it you can't just say grade levels because it's not going to do the right thing passing that information in and check doing an existence check on that tells us that in fact this student and this course task are connected to each other through enrollment that's an interesting thought there yeah i think i think the the important this it's a really interesting question i'm i'm not sure that that question really applies to my particular product maybe we can generally maybe you're looking for a broader answer about a general case but i'll explain why i don't think that matches for my product this product is about home schools so unless you have 10 000 kids in your home school maybe somebody does maybe maybe somebody would try and apply this application to ten thousand kids i think it would fall over long before it got to that point because it's not really designed for that um as i've tried to show it i don't really have it up right now but the the interface for managing a weekly schedule shows like a big screen per student of all of the individuals tasks um yeah i just really haven't designed the app with that like sheer number of um people in mind because of who i'm targeting i'm targeting what amounts to homeschool families so um there are really big families out there i get it so there's you know upper bound maybe 10 um but even in the scheme of like an application 10 is a tiny number right like computers have no problem handling 10. i do agree that if it was 10 000 that would be a different scenario um but i don't think that's going to be the case now there are um there are things called what are they called home school co-ops cooperatives which is a collection of homeschool families who get together maybe maybe there's a future where this product tries to work with a co-op and so at that point then i'd have probably an individual who acts as kind of like the principal or the manager for that cooperative and that person administrates who's in the co-op but i think it i think what that person's view would be would be very um limited compared to the rich data view of the day-to-day of what is what is a parent showing to their kid and what do they have to track so you know i guess my answer to your general problem is at some stage you have to limit the amount of data i think that's the strategy you look at big apps and the way that lots of big apps do it is through the notion of paging so you get a see a page of data at a time um that breaks down if you want a big code down past you like you you know you're talking about with a csv format or whatever so there are can be expensive operations in a system um i just don't think that they truly really apply for this context hopefully that answers your question uh chellio did i get it julio and you get some idea from that okay so looking at this clean method finishing this up we've now got this boolean state and we want all right so we i don't even think we need to assign to a new variable i think we can actually put this check in this if statement so if we have a student and if we have a course task and if this condition is not true right does that sound right to everybody because this is all the this is the case that we're trying to create um so yeah i think this is i think this is right and then we change this pass to raise a validation error that says something helpful [Music] what should it say the student is not enrolled in this course i think that's probably the most accurate statement ah okay best strategy for building a dashboard in django um yeah it kind of again sort of comes down to your problem space a little bit uh and what you're trying to display i think a lot of it does have to do with again what i said about paging like the recognition that there are there are reasons that forum software have like 20 or 30 replies and then say next page and the reason is database performance they don't want to show a million records or you know like some of these conversations can go on for a very long time i'll give you a for instance um my last job currently i work at doctor on demand and we don't have this problem it's a different kind of company my last job was at an ed tech company and it was a space where um middle school kids primarily were being taught creative writing skills so the ability to write um books and you know like short picture books or long form chapter books whatever in this very imaginative dynamic way i won't get into all the details but the important part there was there was a commenting feature and in the commenting feature you could add your thoughts and share and celebrate somebody's book or say get provide some helpful feedback it was a very family-friendly kind of place um but what we found was there was a segment of the population this again this was targeted at at teenagers or middle school so kind of tween age and some of these users found that they could use this commenting feature as essentially a chat room so they would take they would create a book and they would put silly things in the book and like they were these were like primarily tween girls they saw like 12 11 12 year old kind of girls who wanted to chat with each other and so they'd make these silly books and just like treat it like a chat room comment up for commentary comments so there would be thousands of comments and this this one behavior it was a small cluster of people but uh they were even given like a name and stuff because they were such a small demographic that they they had undue influence on the rest of the platform because the way that we pretty much always dynamically loaded comments is that it got to be very expensive and our comments database table was one of our biggest tables within the product part partly because these people would use this functionality as a chat room and database tables as good as they are with database indices and all this stuff they start to hit limits on certain things uh so in that scenario this is this is probably about as close as i can get to your your story it's not quite a dashboard but it's a ton of data and and i think that's that's the real problem is like how do you deal with this data and the best we could manage was to do a page do a chunk of it like an offset of i want this comments from here to here um and i think that can apply to a lot of tabular kind of information which is i think what dashboards are generally most likely to display now what happens when you need all of the dash data to represent your dashboard i'm not really sure um that that gets where it's where it gets tricky is if you if in order to make the complete picture you need every bit of data um that's that's tough oh you're welcome um okay let's see if we got this test right oops uh nope we didn't okay grade i said what did i say ah i said grade levels and it's grade level i think hey cool all right so we've got that and now let's also make sure that the the error itself is what we want so let's um let's what is this going to be assert form and non-field errors i think is the function and we want this to be student is not enrolled in this course hopefully i got that syntax right i did cool okay and if you've never seen this before um there's we're doing all of this in the clean method and in forms in django you can have clean methods that are per field so if i made a clean student field for example and raised a validation error then it would go into a specific error type and the the thing that we printed out earlier um code codalite was asking about html tables and stuff um the form errors are are if you look at it as a data structure and don't try and do the string wrapper on it it is keyed by the actual field names so we could look at it access it like form errors and then student if there was a problem but that doesn't work on the generic clean method the generic clean method puts them in a special field um it's a double underscore it would be they put it in this name by default double underscore all double underscore and those these are referred to as the non-field errors they're not related to a specific field and accessing this way and remembering this dunder all not everybody does that so there's a method that gives you all those errors out of that dictionary so it's probably what i've done here is probably equivalent to roughly like this with the string again you know but i didn't do that i use the non-field errors so that's what that's all about okay so we have what do we have um oh great we didn't need to use the gray level thing awesome i love not importing from other django apps we've got a form we've got uh a form that validates that the task and thing are correct and that's all good um we have the we don't have we're not actually going to be using the create view or update view um our cell directly so we have to do some of the functionality that those things would do manually so um a django form view is when it calls form valid actually that's a good question let's go check it out i'm blanking out on what it's gonna there might be a default implementation of what it calls nope so here well i guess this is a default implementation it's just not what i expected we have a form valid that will be called by the form view when is valid is true and this is our opportunity to actually save something and the thing we're trying to save remember we're trying to make a connection between a student and a task and that thing is coursework so we're going to need a save method that will actually create the coursework or it needs to also be smart and look for an existing coursework and update that if it exists those are the things that we need to check so we need a couple more tests before we're really done and we'll say test save new coursework okay and a new coursework is created for a student and task and we've already checked the clean state so we want we want the same kind of data that's up here this valid state in order to do the work and so we've got this the the difference between this is now i typically follow the aaa test pattern and that stands for arrange act and assert so as you've been reading my tests along with me you may not have noticed it but i always space them out in the same way and that way is to say here's the arrangement and that means all of the data set up so that's this first block at the top then a space and then this middle line this is the act and then the third is the actual assertion section so that pattern i think is a useful one it makes my test very clear about what they're trying to actually test so when we get down to this create new coursework the arrangement we actually need to call form is valid as a precondition so that's that's um just the way to go and so now we want to say the action that i want to act upon is a form save method save method doesn't exist it's not something that just comes with a form by default um is that true well is that that true of a model form [Music] i might be lying about a model model form might have a default save action in which case maybe it's doing the right thing we'll play with this um so what we want to come out of the bottom here is we want there to be a coursework so we want to assert that there's a coursework that matches this the necessary input conditions so that's object filter and student equal student and there's a course task equal course task and i guess we could check the completed date but frankly i don't feel like it so we want to assert that this is true so let's just say assert that it exists uh which way i guess we need the coursework model okay from homeschool students models import coursework oops not object objects now notice it didn't it didn't fail on the save call so it tells me that there is a save method all right that's interesting so it'd be really nice if we didn't have to if we didn't have to do any extra work if we could just call save on this model form and be done don't know if that's the case or not we're about to find out so the other condition is and frankly if this is the case i might actually delete these tests because what are they like if we can prove to ourselves that the model form is doing its job why am i testing the model form like i would be just testing save and there's really no value to it in fact i'm not i'm going to assume that maybe we can get away with not doing this and so let's go ahead and create um well oh junk i'm afraid of a couple things what am i afraid of this model form takes three attributes student course task and completed date and what happens if i change one like i could have the student and course task be the same consistently what happens if we change the completed date am i gonna create a new coursework with uh with a different completed date value that would be not so great um i don't know that if i have a unique constraint unique together on the student and course task so there's other there's other things that we get to figure out so let's go back to the students models and look at the coursework for a moment um yeah i don't have any constraint on this so there's ways that you can add a constraint that will say that the student and course task must be unique together you can't have more than one of them that might be something worth adding for this form i don't know if it'll do the right thing but we can try it if it doesn't give us the behavior that we want yeah it might just raise a different kind of error i'm not really sure and we want to check let's actually do it this way so we'll do count if it count equals one then it's a singular coursework but let's do a coursework factory and pass it the student and the course task and we gotta import that thing of course uh all right what do i not have a factory for this oh man bummer all right let's go see if there's a factory no there is i just typed something incorrectly oh that's schools okay i'm just in the wrong spot yeah put it in the wrong spot okay yeah those are what i was afraid of okay what happened what happened is we told it to make one and it dutifully made one but it made another one okay is there what we really want is to update an existing coursework and this is where we might need to override the save method on the form because it's not quite doing what we want but before we do that sorry just whack the mic before we do that let's go let's go check on the model form implementation of save take a look at some django for a little bit and see if it is helpful so i have a local clone of django because i look at enough that it's valuable so we're going to open that up and we're going to look for the model form class in here and it's got the form mix in and the single object mix-in is there a save method in here come on wait i bet it's in the form mixin maybe i don't know hold on we're gonna keep hunting i don't know if this is the right spot right now but we're going to check for a minute yeah i think we're gonna have to get a little more creative well rats they're doing the right thing for most circumstances i should just be really clear about that i think that the the action there is appropriate um it's just not quite the behavior that we want in the circumstance and there's probably some clever way to do essentially what we're looking for is like a insert or update kind of operation and i don't know that that really exists with model form i can poke around django or update creator update there's a get or create i know this exists i just don't know that it's going to work with a model form get or create model form yeah they're pretty much this person's pretty much just saying what i was thinking was going to happen and this isn't even right be careful with stack overflow so the reason that this is wrong is the i think that i think this is wrong is that they're creating this instance in this example based off of any attributes in there which means that this book has to match exactly so for for our scenario that we're talking about we want to fetch um if there's a student in task but we would like to update if there is a different date that's been inserted if it's been changed so that's be careful just be careful what you find on stack overflow it's i generally love stack overflow it's a good resource but just got to be watch out for it sometimes okay so this is close to right this test is basically right if we take out the course factory um i'm gonna make a new test i'm gonna leave it as one i'm just gonna take out this attribute so this should pass i think what oh that's because it followed this one maybe um we'll do that try this good the test passed let me check on that link skelio cello i'm sorry um oh this is cool is this these the same docs that are this looks like the same docs from django project just in a slightly different format i've never heard of this site before it's kind of cool nice i i've never used that before i've never encountered this site but um that doesn't mean it's not good it looks kind of nice nice and clean um okay so we've got this test this works in the scenario of one and but we want it to be in this scenario with two or wouldn't it with an existing one and also return one but it's returning two so that's what's wrong all right so we do need to make a save method after all we're going to write our own save method and we're going to do save and i don't think we even need to take any arguments so we'll say create or update a coursework yeah that's right um i think i may want to use get or create that might be the right action we just have to be a little bit more careful than that stack overflow um yeah well i and i always get it backwards too so let's go read the documentation on ghetto get or create because it's it takes some parameters which are are um there's two two kind of modes to it of parts that you want to use for lookup and parts that you want to update and i always get it wrong on what those parts are because there's this defaults thing and i can't remember if defaults is where you want the values that aren't going to change or it might not even be this one it might be is it update or create oh man i could be even just using the wrong one and misleading you all yep update or create okay so here's the pattern again i think i because i don't use this often enough i have to stare at it every single time um so you can see this pattern is it's kind of it's looking up well let me let me just read this i need to focus and i know sometimes it's hard for me like read docs and talk to you all so i apologize we're just gonna quietly read together for a second hopefully that's not too boring for you um and we'll see okay so it's trying to fetch based on the keyword arguments if a match is found it updates the things passed to defaults that's what i always get i can never remember is it looking based on defaults or is it the other so i think what we want is um coursework dot objects dot update or create and we want to look up the student um uh yeah my i'm gonna be honest with you i have very limited knowledge of django with machine learning my my feeling is probably so here's what i know roughly about machine learning machine learning is a bunch of data analysis right and you're generally trying to parse through a ton of data to generate some kind of model that you can then use so i think the answer to your question is is do you already have a model that you're trying to just utilize um if so i think there might be like clients that can kind of just read that model and take action but again i could be just talking nonsense um if if it's you have to build the model and hey stupak welcome i still haven't done the uh the dark theme i'm doing great thank you um if you have to build the model then you know i don't have a fantastic answer for you i think that gets pretty complicated with um pipelines and and do you which kind of background workers are well suited for that and it's probably not necessarily something like salary you might need whole tools like luigi that has all all that stuff you have the model but don't know if so i guess my question for you chilio is is uh how what is what are you trying to to do with the model is it something you like you have a model and you want django to display it on a web page if if that's your what you're looking for then that seems doable if there's a client that can read the model pretty easily but i don't know that i have a whole lot i can offer beyond that but that would be my thought is that if you already have a model then i think django should be capable of processing data from that and and displaying stuff based on it but i don't have a great answer i gotta believe that it's possible because they think about um companies like instagram are i know i don't know how much they're they've they've pretty dramatically changed how they've used django over the years as they've had skilled big big scaling issues but you got to believe there's machine learning all over the place with that company um would be my guess so yeah i gotta believe it can be done i just don't know the best ways to do it and i would love to learn more at some some future point but i've i've yet to hit a case that i really need machine learning so we're going to do our update or create here yeah no problem we'll do our update or create and we'll say course task course tasks those are the two lookup fields that we care about so we want to create if we want to update if those two things match already and we want to create if they don't match so the last thing we need is this defaults dictionary and defaults is equal to completed date and we pass it in self cleaned data completed date i think that's what we want um oh i wrote something unparsable oops there it is okay so we have a save and i'm goofing all this stuff up a student is not just something that's magically accessible so we need clean data student and we need clean data course task and the precondition for calling save like the conventionally remember save is just a conventional thing is you would you would not call this method unless you've validated and so by the time you validate it'll have gone through all the other checking and clean should actually have values at that point if it doesn't you're breaking the the contract that that these these things are trying to offer so i think this is the right call i think it ends up being pretty uh pretty nice little short and sweet save method if i did it right um we'll check got one so let's go back up to the top of this test test the whole thing great got all four in half less than half a second that's exciting so that means that we now have a save okay we might be at a a breaking point for at least my brain we'll talk about it for a minute and see where we have to go and maybe maybe the rest of this goes in a future stream i'm not really sure yet we now have a form that will clean all the stuff it will save it if you want to which is good you know what let's let's keep going i can power through this because we're so close we're like we've got i think all the major pieces we just need to wire the last few things together so let's i think our form is done aside from this to do which again this is like i can handle this later when i don't have to bore you with silly uh validation stuff so we're going to go back to our forms or excuse me our views and i'm going to make less sense as this goes on y'all but i'm doing my best we're going to go to the coursework form view and we have our template oh we're definitely not going to get all this done because this has got the um we haven't touched the template at all yet okay well we're going to what we're going to do let's let's draw a line in the sand then where we'll end this stream this evening is getting the view stuff together so that the form and the view are all talking nicely and add the behavior and um i think that gets us pretty darn close i think there might be a couple things with like dealing with next parameters and some other stuff but that's that's okay so um what we're still looking for is the connecting the form valid callback to the save method so that's the other piece so let's return to the test views and we have our our get here but we really need to do a post [Music] and that will be where should we put that let's put i like to like have these other kind of tests these fringe checking tests be sort of the bottom if i can and in my test case i always want to check that the authentication the access controls there i want to test the happy path case for doing a get request to make sure that's working and then i want to test the case for a post and make sure that that is also working those are important the question is is how how am i building up the data or what i want to pass to this form that's really that's probably one of my bigger questions because i don't want anybody tampering with the post data in fact i might not even put the post data into the template what am i talking about so remember there are three parameters that are going into this coursework all that the user really has to provide is the completion date the other stuff of the task itself and the student are going to be coming from the url because we've got the uu ids up there so i think that i i'm going to assume that the way i'm actually going to do this for the how the stuff gets built is i'm going to make the view pass in the proper ids and if that's not clear you'll see it in a second i hope so let's say test post and a post creates a coursework i'm not going to test we don't need now because we have the form test we don't need to check both the create and update case because we tested those in that other spot but this is kind of the end-to-end test to make sure everything is working at to my desired expectations so we have the post i'm going to create all the necessary data to start and paste it in here and we want to have a response and we want to check that the response is a 302 um that's that's saying it's going to be doing um a valid redirect actually i think there's even a better method for that there's a assert uh what is it no no no no no response there it is all right response 302 i think okay and delete that so here's our new test it's not going to be a check it needs to be a post so that's all good and then we need to pass in a data dictionary and the data this is where we need to get a little different the the data as i said i want to pass in um i want to pass in the date the completed date and so that i make this test like continue to work in the future i want i need to pass in there's oh man there's more checking i forgot to do we're not going to finish it all dang it there's more stuff um here's what i forgot uh well we'll get to this in a second for now we'll just say course the we'll give it the course start date okay we have a data dictionary before i forget i'm going to come back to the the forms and i'm going to leave myself another to do we've we forgot a thing to check and we were so focused on correcting or making sure that the student and course tasks were correct that we forgot about the completion date so what if the completed date is outside of the school year so somebody types in you know one one one as in like the beginning of when we started i mean that's not really accurate but a long time ago then that would be um bad and so we need to make sure that that is within the bounds it's going to complicate stuff but we're going to i'm going to ignore that that's more validation that you all can go look at my commits in the future because i'll probably work on it this week while i'm offline so we've got our post and the other thing we need to check is we need to check that there is we want to assert that there's a coursework that matches the student and the course task there's got to be at least one when we're done or actually not at least we want exactly one and we need to import the course work which is in the students models all right let's try it out it failed the course has no attribute start date oops um well shoot you don't day of week model ah what's going on it doesn't it doesn't know when it's running that's not surprising okay i screwed that up so but i do know that the the school year does so we can do we can change this to grade level school year that has a start date for sure hey codelite thanks for coming in appreciate your questions it was really thoughtful thanks for sticking around okay we did a post and if you looked at it you saw that it said a 200 instead of a 302 what that's telling me most likely is that there were form errors so let's do let's add that extra assertion here so we'll say well can we get the form errors it's going to be in the context so for now we can we can just confirm so since it was a 302 it's gonna or a 200 it's going to have the form and the context and we could say um assert not errors and we'll see some stuff probably okay that's exactly what i was hoping to see so here's where we're going to go into the the actual form view and give it these extra parameters i'm i deliberately left them off of the data the payload because i want the view to be able to provide that from the url parameters so that way it makes it tamper proof so nobody can submit form data that includes somebody else's student and somebody else's course task and monkey with that and cause me to validate yet another thing by limiting it to building that ourselves into the view we bypass that problem but in order to do that we have to override i think it's the get form keyword args i think that's where we need to go back to the classy class pageview so we need to make sure we call the super on that and pass in or maybe this is get initial i always get these mixed up return the initial data what's the prefix i don't know what that means i don't remember what that means no no no it is it's definitely in the keyword arguments because the initial that's something else so we don't want the initial the initial is like if you were populating the form for the first time and wanted to print it out there which i don't think we really want to do i want to put it in the keyword arguments so we need self keyword form keyword arguments and we want to call um super and get the form keyword arguments and let's return those just make sure we'll run the test again it's going to still blow up but i want to make sure that i called this appropriately at least yep okay so it didn't blow up in a new way because we didn't mess that up this is where we want we're going to have to extract here so we want a cache property then i'm going to call student and i'm going to take this put it here and change this to i seriously don't have cache property here that's a bummer okay so returning that and cached property comes from django utils functional cache property as the name kind of suggests it makes it a property and so that we can do this we can do self dot student so it's going to be like an attribute access but it's going to execute property method and the caching of it is so that it won't calculate it more than once because this is doing a database query and the reason i'm putting it here is because we're going to update the keyword args with the extra information that was missing so i'm going to say student is self dot student right here okay so hopefully that made sense if i'm just extracting this out and so that it can be used both in the get context made method and the getform keywords method if i run the test again hopefully we're down to one error shoot form class got an unexpected keyword argument student okay well stinker that's not not a keyword argument for the form it is does it go in initial that doesn't seem right maybe it is all right maybe it's initial see if i have a get initial anywhere else yeah i have a few of them okay we'll just tweak this around to use initial all right try again oh i messed up i'm getting tired y'all i can tell making i'm starting to make silly mistakes that i should be able to catch i didn't even do it right so what what happened there we have in theory i thought that passing in the student um would populate that field so that it wouldn't act as required in the data i'm disappointed that that does not appear to be the case hmm is it one of these other methods i think the problem is is that the the initial okay this might be it the initial is only used on the get request as i think i mentioned or all right i maybe didn't mention it but it's now coming back to me that's why putting it in initial is not the right spot so i thought i could do with keyword arguments but that's clearly not correct either and so get form keyword arguments this is maybe maybe it was the right thing to do to put it in form keyword arguments but i put it in the wrong spot what we need to do is replace the request post with a different dictionary all right and maybe i have let me see if i have other examples of this because well maybe if i did i got rid of them or no or did i search for the wrong thing i think i just searched the wrong thing i did again that's that's what fatigue does to you so that's adding the adding an attribute of the keyword arguments that's not what i'm looking for all right i don't think i'm going to find it quickly but i think i know what i want to do i want to take the form keyword arguments and let's rename these again go back to form keyword arguments and call this quarks again now the difference is what on i've got to be careful with this too it needs to be the right method on a post i need to update this with the data attribute now that's probably something i could realistically search for maybe not as fast as i would like though okay well we won't go off past effort past work so if self request is a method sequel to post then we want to do we want to insert our data into the actual post data so we know that the keyword args from calling super already has the data key and so i want to say data is equal to um we want to copy i think we want to copy the post because what if i tried to do this if i tried to say keyword args data and then try to insert the student in here directly well let's just do it and so you see what happens i think it's going to tell me that that is immutable yeah the query dict is immutable so what you have to do is this little song and dance where you pull out the data as a copy so we say i think what and i think you can call copy on the query dict i think that works so we'll say data copy and now we can say um we can add the student and then at the end replace data with this new copy i think that works what did i oh i still have this thing that lines wrong excellent that's that is right that's what we wanted because look the student is no longer as a this field is required so we put that in properly so really it's not just and it's not just i mean i could set this attribute by attribute that's kind of annoying though so we can do an update and turn this into a dictionary like this and course task of self course task which we have don't have yet so we'll get there um and if we run this test right now it's going to fail because course task is not a property so it doesn't know what that is so we're going to take this chunk of code right here move that down here and we'll say cache property def course task and now we're going to return this and add a self onto here so i think you can see that the impact of this is it starts to make the context really clear in fact we probably do an update on this we could continue to refine get it down to one line why don't we let's do that and say context update okay the student is not enrolled in this course okay that's well we're at least getting somewhere the we're missing the enrollment that we put in that other form so we can go um we've got this and we can return to the form get the enrollment to show that these are connected and now run this ah okay almost at the end this is like the finish line so we need to give it a place to go to and that means we need a success url so let's look for get success url there's got to be one in here somewhere and we're at this we've got i'm going to keep all of these get methods together because they're at the interface that goes with this form and we've got to get successful url so where does this go back to that's the question we want it to go back to the student's uh course view which took as the arguments it took the student's uuid which was in this view it was just the uuid and the course task uuid so that's going to be we can say self course task course uuid whoops oh no now now ours is passing now it's doing a that now this form is not in the errors because it's actually doing the 302. so we delete that and oh oh okay i forgot to save the thing good thing we wrote a test right um so the view is all cleaned up i think this is pretty darn close to where we want to be the last thing to do is we need to actually on the form valid this is one of the hooks that's there it's going to take the form and we want to say form dot save and that will cre that should create the course work or update the existing one oh but it's supposed to return um so we need to call form not save and then we want to return the super call form valid form finally all right that was a lot but what seemed so i guess the summary of this is that what seemed like this not very challenging feature of can we click a little calendar icon come to a page that will let us set a date save a thing so that it completes the task for a student it was a whole new form a whole new view a view that required custom logic um a lot of tests there's more validation that i haven't even done yet um so it was a bunch of stuff hopefully you found that journey enjoyable uh or at least you understand everything that went that goes into this it's not and this is just well just i don't trivialize it this is this is what it takes to make a server rendered view of this imagine the effort that would happen if you had a javascript based version so not only would you have to do some form stuff instead of making a view because we haven't even touched the template yet instead of making a view you would make an api and it would do some json serialization uh and then you'd have to go over into javascript lan and do all these kind of things and make handling code over on that side too so i think it's just trade-offs of what you prefer um i do think that in the end this ends up being pretty tight and clean and it's going to be very well covered in test coverage um there's i think there's a more to do like there's a get a next parameter and return to that is something that's gonna i can almost guarantee that my wife's gonna be asking for that because she's not gonna wanna just be dumped right back to where she was she's gonna wanna see um the item that she just clicked so there's that um so there's there's additions that need to be made here there's there's refinements on the checking to make sure that there are those extra edge conditions about completion dates being out of bounds and all these sort of things but that's been this flow and that's that's what feature development on this project looks like um i i think i'm my my brain is totally fried at this point i hope you've enjoyed this journey um i will take this content post it on on youtube if you're still around and if you wouldn't mind uh if you're watching on youtube later give me a like or subscribe down below that'd be really awesome i think that would um help me be discovered by more people and i just want to say thank you for joining in and i will see you next time take care you
Info
Channel: Matt Layman
Views: 735
Rating: 5 out of 5
Keywords: twitch, Python, Django, SaaS
Id: -aQLfHyApGk
Channel Id: undefined
Length: 147min 51sec (8871 seconds)
Published: Thu Jan 28 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.