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