Building Data-Driven Web Apps with Flask and SQLAlchemy Transcripts
Chapter: Client and server-side validation
Lecture: The register viewmodel
Login or purchase this course to watch this video and the rest of the course contents.
0:00 Well I think you'll agree that this index method is now simpler than it was before 'cause we've moved the specific type of behavior
0:08 operation we had to do there over into this class. It's not super compelling. Let's look at something
0:14 that is going to make a big difference for our world. So over here, we are going to have a register operation and we've already implemented this, right
0:21 but just not as nice. So here it's definitely going to need a User ID, passed across. And down here, we have to actually go and get the data.
0:29 And create this data dictionary thing, get the data out. Normalize it by lowering and stripping things like the email.
0:37 Doing some validation, if the validation fails we have to return the data they have entered in order to round trip it. That's pretty important.
0:45 And then we're going to do some other stuff some other validation, to try to create it and also round trip the data if that fails
0:51 with the new error message. And who I set it on so let's make this a lot, lot simpler. So I'm going to do that by going down here to my view models.
1:01 My Account, and I'm just going to copy and paste to make this as easy as possible. We'll say Register. Like that.
1:08 And I'm going to change the name, of course. Register view model. Now, we are not able to query the user in this case. What we want to do is
1:17 this is what they're entering to create the user. So, go and bounce back and forth a little bit here
1:24 so first, we're going to come down here and say the view model is this and we got to import that as well.
1:30 Now notice, we're creating this data dictionary thing here and we're actually passing the default value which, if we go down to the Base Class.
1:38 I think we're, yeah, passing exactly the same default value so this should work completely fine there. So we don't need this data thing, and in fact
1:48 this part is going to be part of what our view model does is capture that data, and normalize it. Okay, so we can just move that into the view model.
1:57 And this will be, we don't need that part. 'Cause it's hopefully not going to work. There we go. And this will be, request_dict. Okay, super
2:08 and because the default value of this shouldn't fail even if it's missing for some reason. Alright great, so that's working.
2:15 Now, instead of doing this validation here let's say. Somethin' like this, we can say. vm.validate. That doesn't exist yet, so let's go and
2:26 let me borrow that real quick. And we'll go and write it. Let Pycharm write the first bit there. So what do we need to do, we need to say...
2:35 And we'll just leave that there for a second we'll say if, if not self.name. Or not self.name.strip(). Right, we want to make sure space isn't counted.
2:45 Then what we got to do is say self.error. Remember the error is coming from the base class. It is something like, You must specify a name.
2:56 We can say elif. What do we want to do? Well, let's do the same thing for the email. It's a little bit tedious down here, but you know what?
3:06 It's fine, because it's just jammed into this class that we don't have to think about it it's not going to mess up the rest of our method
3:13 so, we must specify an email maybe we'll put periods on those. And then password, as well. Right, so if there's no password you must specify a password
3:24 and we could even do additional stuff like let's go down here and say. Else If the length of password. Is less than five.
3:36 Say you have to have five characters. Well I could even do a strip on it as well. That's a lot better than just Some fields are missing.
3:43 Right? So how are we going to use this back here? So, we're going to go validate then we just do one test no matter how complex the validation gets
3:51 we just have, is there an error? If there's an error, we have to do this we have to round trip all the values they passed in.
3:59 That user ID that may be coming from the cookie things like that. Well, it turns out that's bundled up in the view model already
4:07 so all we got to do is say vm.to_dict. That's it. Cool huh, and then down here we're going to use this data so vm.name. Email and password.
4:20 And if for some reason we can't create it. Something goes wrong here, instead of all this again, this just goes. vm.to_dict.
4:29 And if we change the template and more data has to be exchanged. No big deal, we're still just going to do the same thing.
4:35 Look at this. To me that looks way, way nicer. And in fact, there was a test that we forgot. We forgot, over here
4:44 that we should probably check that the user doesn't exist. But let's go ahead and see if this is working and then we'll go add that test.
4:51 So I'm going to go log out, and try to register. Let's go Register. My name'll be user2. My email address will be email@example.com.
5:01 Give it a password here. Should register. Oh, now look at this. I don't think we've run into this before. But this is something
5:09 that's really tricky with SQLAlchemy and I could of course just edit this out and fix it but I want you to see it.
5:15 Remember back in the SQLAlchemy section I said, We need to make the factory a little bit nicer that session factory a little bit nicer
5:22 because, we could run into this case where we insert something in the database commit a transaction
5:28 and then the object becomes out of sync with the session. And that's what's happened here so let's go fix that real quick.
5:37 Bit of a diversion, but it's certainly worth it 'cause when you see this, it's like, Oh my goodness what do I even do with this?
5:42 So let's go over here, the data bit is all we need to work on. So this db_session thing here. So we have this session, create_session
5:50 and this is what we're calling to get this factory. But we come over here. And do a little work on our session first we can see the session is this
5:59 and return of the session and that's like a simple refactoring. And we can say that this is a session. Which comes out of SQLAlchemy.
6:07 So we can say session.exprireoncommit. So normally, SQLAlchemy tries to be as safe as possible. If for some reason you commit some data
6:19 you can no longer work with it. Because, for some reason it maybe has to be requeried from the database to do with transactions and whatnot.
6:26 So I can come down here and say You know what, that's, in this particular case we don't have this problem. Right, we're fine just showing
6:34 what we just saved to the database. So we can say session.expire_on_commit = False. Which will make this issue go away.
6:39 So let's go back here and just change this. Set a password here and let me just make sure it's restarted. It's still running so it will have.
6:49 Now, let's do the same thing. Boom, fixed. Okay so, if you run into that, session has been detached.
6:56 Think carefully, if this is okay for your application. But if it is, this is the fix. Okay, just go and say
7:02 Session, it's okay to still let me look at the values of an object after it's been committed and inserted into the database.
7:10 Alright but, that sort of overshadows the fact that our view model is doing its thing let's actually log out
7:15 and just do a little bit of the validation here so if I just go Register, I must specify a name. Before it just said fields were missing, right
7:21 so if say My name's Michael. We try it again, now my email must be specified. Let's see. Put that one. Now I'm going to specify a password
7:30 how about the letter a? Nope the password must be five-character. ABC, how about A one two three four five six times?
7:41 Now we're back here, what was the error what was the problem? Actually, we missed something really quick if we look over here
7:48 'cause it didn't tell us what the error was it just didn't let us continue. And here, what we need to say
7:53 is that the error is, The account could not be created. If we try again, now we get The account could not be created. But it would be better to say
8:02 A user already exists with that email address. Did you want to sign in? So let's add one final test over here and we're going to add a test
8:10 and it's not going to change a single line, here because, we've moved that validation somewhere else. So this was just testing the values in memory
8:19 but we could go further. We could say something like this. If user_service.find_user_by_email(self.email) If that exists, this is an error.
8:32 Self.error= A user with that email address already exists. And we could go ahead and even make this an elif. Here I suppose.
8:43 Okay, so we have our sort of standard data validation and we have deeper validation checking with the database and whatnot.
8:51 So let's try again, instead of getting this error message that's like Whoa, Create failed. Now we get A user with that email address already exists
8:58 So how about Jake? Over at Talk Python. Is that going to work? Yep. Let's see. That's probably not so good
9:08 'cause I should have set my name, up here, to be Jake. Right, that's why it said Michael. Little confusing, how about Sarah, and I'll do Sarah.
9:18 Here we go, welcome to your new account Sarah. Everything is looking great. So let's just review real quick here. How this made this a lot simpler, so
9:27 all we had to do is move all the data exchange into this section. The validation here. Let me just check if there's an error.
9:34 That's it, this is almost the entire behavior that we needed, but now it's over here in a nice, isolated testable way.
9:42 I guess the final thing that we could do kind of round this out, is up here we also have to do this business here.
9:47 So let's just say vm = RegisterViewModel and here we'll say vm.to_dict because maybe eventually, they'll be like dropdowns
9:54 or other stuff that are populated in the constructor there. So this'll help future-proof that. So, you can see how the view model
10:01 is super massively simplified both the data exchange, and the data validation. Hope you like this pattern
10:10 once you get used to it, I think you'll really love it it's absolutely powerful both for creating forms with proper validation as well as testing
10:18 because now you don't have to call the web method you just interact with this object like a standard Python object, which is cool.