1 00:00:00,277 --> 00:00:04,585 In Flask and in Django, people often get the wrong idea about forms. 2 00:00:04,585 --> 00:00:07,980 They hear the word form, and immediately think about HTML forms. 3 00:00:07,980 --> 00:00:10,040 They think that forms are all about display. 4 00:00:10,040 --> 00:00:12,830 And really that makes forms very limited. 5 00:00:12,830 --> 00:00:14,480 Forms are about validation. 6 00:00:14,480 --> 00:00:16,650 Making sure that your data matches a certain pattern. 7 00:00:17,820 --> 00:00:20,450 The de facto form library for Flask is Flask-WTF, 8 00:00:20,450 --> 00:00:24,620 and it builds on top of an older package named WTForms, and 9 00:00:24,620 --> 00:00:27,040 we'll actually end up with both of them installed. 10 00:00:27,040 --> 00:00:31,082 You'll install this with pip install flask-wtf. 11 00:00:31,082 --> 00:00:37,190 This package also provides us with CSRF, or cross-site request forgery, protection. 12 00:00:37,190 --> 00:00:39,110 What's cross-site request forgery? 13 00:00:39,110 --> 00:00:41,360 Well, imagine you've logged in to your bank site and 14 00:00:41,360 --> 00:00:43,500 it keeps you logged in through a cookie. 15 00:00:43,500 --> 00:00:46,990 Now, imagine that some horrible person out there sends you an image in an HTML page. 16 00:00:46,990 --> 00:00:52,920 But instead of an image URL, the URL goes to some send me $1,000 page on your bank. 17 00:00:52,920 --> 00:00:55,770 Without CSRF your bank trusts that you actually made that request, and 18 00:00:55,770 --> 00:00:57,730 goes ahead and sends them the 1,000 bucks. 19 00:00:58,865 --> 00:01:02,550 CSRF includes a custom one time code with each submission. 20 00:01:02,550 --> 00:01:04,520 And, if the form doesn't have that code, or 21 00:01:04,520 --> 00:01:07,260 doesn't have the right one, the request is ignored. 22 00:01:07,260 --> 00:01:08,780 Okay, let's make our registration form. 23 00:01:10,220 --> 00:01:11,502 All right, so if we're gonna forms, 24 00:01:11,502 --> 00:01:15,530 our app.py, I can already tell it's gonna get pretty crowded. 25 00:01:15,530 --> 00:01:17,080 So, we probably don't wanna put them in there. 26 00:01:17,080 --> 00:01:19,188 And our models.py should really just be models. 27 00:01:19,188 --> 00:01:21,585 So let's add another new file. 28 00:01:21,585 --> 00:01:25,192 That we will call forms.py. 29 00:01:25,192 --> 00:01:26,831 Very creative, I know. 30 00:01:26,831 --> 00:01:27,966 This is where we're gonna build the forms. 31 00:01:27,966 --> 00:01:31,640 Now, our form that we're gonna build, I wanna warn you. 32 00:01:31,640 --> 00:01:32,850 There's a lot here. 33 00:01:32,850 --> 00:01:34,580 So we're gonna do a lot of stuff. 34 00:01:34,580 --> 00:01:35,550 So here we go. 35 00:01:35,550 --> 00:01:36,250 Okay. 36 00:01:36,250 --> 00:01:39,360 From flask_wtf import Form. 37 00:01:41,100 --> 00:01:45,760 And what's kinda weird is that flask_wtf doesn't use the flask.ext thing. 38 00:01:45,760 --> 00:01:47,400 I really wish it did, but it doesn't. 39 00:01:47,400 --> 00:01:51,840 And then we're gonna say from models import User. 40 00:01:51,840 --> 00:01:56,010 And let's start building our class. 41 00:01:56,010 --> 00:01:57,920 So we'll make a RegisterForm. 42 00:01:57,920 --> 00:02:00,420 You can call it registration form if you wanted. 43 00:02:00,420 --> 00:02:02,932 And it's going to be of the class form. 44 00:02:02,932 --> 00:02:04,370 Form is the parent class. 45 00:02:04,370 --> 00:02:09,090 So Username equals StringField. 46 00:02:09,090 --> 00:02:13,990 And then this first argument that we give here is the label. 47 00:02:13,990 --> 00:02:20,180 So if you think about forms as they show up on HTML, there's a label, right? 48 00:02:20,180 --> 00:02:23,000 So we're gonna put the label of Username. 49 00:02:23,000 --> 00:02:27,980 If we think about our Username field, there's some things that have to be valid. 50 00:02:27,980 --> 00:02:29,660 For it to be a real username, right? 51 00:02:29,660 --> 00:02:31,630 So, there has to be data. 52 00:02:31,630 --> 00:02:33,140 We probably want it to match a certain pattern. 53 00:02:33,140 --> 00:02:37,420 And we should probably make sure that it's not already in the database. 54 00:02:37,420 --> 00:02:41,070 Though, these requirements here, we call these validators. 55 00:02:41,070 --> 00:02:45,490 So we actually have an argument here, called validators. 56 00:02:45,490 --> 00:02:47,770 And we can put in validators. 57 00:02:47,770 --> 00:02:51,061 So, I guess that we want there to be data, right? 58 00:02:51,061 --> 00:02:55,670 So DataRequired is one of the validators. 59 00:02:55,670 --> 00:02:58,190 And then, the others we kinda have to create our self. 60 00:02:58,190 --> 00:02:59,380 Well, the regular expression we don't. 61 00:02:59,380 --> 00:03:03,469 So, we want to match our patterns, so we're gonna use Regexp, 62 00:03:03,469 --> 00:03:07,503 which is a regular expression, regular expression pattern. 63 00:03:07,503 --> 00:03:09,502 So, what are we gonna give it. 64 00:03:09,502 --> 00:03:10,740 Well, we have to give it a pattern. 65 00:03:10,740 --> 00:03:13,600 What would our pattern be? 66 00:03:13,600 --> 00:03:18,780 Well, we want it to start, and we only want a through z lower case, 67 00:03:18,780 --> 00:03:23,270 A through Z upper case, 0 through 9, and then underscore. 68 00:03:23,270 --> 00:03:27,870 And we want that to be as many of those as, as they want, at least one, and 69 00:03:27,870 --> 00:03:29,610 then we want that to end. 70 00:03:29,610 --> 00:03:33,348 Why didn't I just use /w with a plus sign? 71 00:03:33,348 --> 00:03:36,110 Since we're gonna be showing this in the URL, 72 00:03:36,110 --> 00:03:39,190 sometimes unit code doesn't play nicely with the URL. 73 00:03:39,190 --> 00:03:43,900 So I figure it's safer just to restrict them to using ASCII than it is to 74 00:03:43,900 --> 00:03:47,780 worry about something looking weird, or not actually loading, or, or whatever. 75 00:03:47,780 --> 00:03:49,840 If you wanna go with Unicode or 76 00:03:49,840 --> 00:03:53,210 you wanna do some other requirement, then that's fine, go for it. 77 00:03:53,210 --> 00:03:55,600 But this is the one that I wanna do. 78 00:03:55,600 --> 00:03:57,019 Okay. So that's our pattern. 79 00:03:57,019 --> 00:03:59,258 And I'm gonna pass in a message. 80 00:03:59,258 --> 00:04:02,954 So, my message is going to be, 81 00:04:02,954 --> 00:04:07,791 Username should be one word, letters, 82 00:04:07,791 --> 00:04:11,934 numbers, and underscores only. 83 00:04:11,934 --> 00:04:16,769 And look at that, I'm way out on column 95, so let's do. 84 00:04:16,769 --> 00:04:18,100 I'm gonna make sure I can break this. 85 00:04:18,100 --> 00:04:19,320 I'm gonna put a parenthesis here. 86 00:04:20,460 --> 00:04:22,640 And I'll close the quote here. 87 00:04:22,640 --> 00:04:23,340 Open a new quote. 88 00:04:24,430 --> 00:04:25,550 Close that parenthesis. 89 00:04:25,550 --> 00:04:29,390 This is a really handy way of not having to like, invalidate a line break. 90 00:04:29,390 --> 00:04:32,180 Just a little, quick little tip here. 91 00:04:32,180 --> 00:04:37,080 Okay, and then that closes our Regexp thing. 92 00:04:37,080 --> 00:04:40,182 Before we forget, let's go up here and actually import those. 93 00:04:40,182 --> 00:04:43,999 So, from wtforms import StringField, 94 00:04:43,999 --> 00:04:48,501 because it's StringField that we're using. 95 00:04:48,501 --> 00:04:54,091 And then from wtforms.validators 96 00:04:54,091 --> 00:04:59,870 import DataRequired, and Regexp. 97 00:04:59,870 --> 00:05:01,698 So far those are the only two we need. 98 00:05:01,698 --> 00:05:02,580 All right. 99 00:05:02,580 --> 00:05:06,150 As I said, we also wanted to make sure that name did not already exist. 100 00:05:06,150 --> 00:05:06,920 Right? 101 00:05:06,920 --> 00:05:10,980 So we're gonna write our own validator that we're gonna use here. 102 00:05:10,980 --> 00:05:14,220 And we're gonna call this name_exists. 103 00:05:14,220 --> 00:05:16,320 Then we close our list. 104 00:05:16,320 --> 00:05:17,934 And we close our StringField. 105 00:05:17,934 --> 00:05:20,635 So let's go right name_exists. 106 00:05:20,635 --> 00:05:24,642 So here we go def name_exists, and this takes two arguments, 107 00:05:24,642 --> 00:05:29,477 it takes the form, that it's running on, so in this case, register form, 108 00:05:29,477 --> 00:05:33,881 and it takes the field that it's running on, in this case username. 109 00:05:33,881 --> 00:05:39,580 And we wanna do if User.select .where User.name, 110 00:05:39,580 --> 00:05:47,062 oops sorry we called it username, is equal to field.data.exists(). 111 00:05:47,062 --> 00:05:49,710 So that just returns a Boolean of either true or false. 112 00:05:49,710 --> 00:05:51,810 This record's here or this record's not. 113 00:05:51,810 --> 00:05:56,430 So, if that comes back as true, then we want to raise ValidationError 114 00:05:57,560 --> 00:06:02,170 of User with that name already exists. 115 00:06:03,810 --> 00:06:07,505 But look, we imported something else and we need to, or we're using something else, 116 00:06:07,505 --> 00:06:08,655 we need to import that. 117 00:06:08,655 --> 00:06:10,545 And we need to import ValidationError. 118 00:06:12,985 --> 00:06:15,815 We've done a lot and we've only created one field. 119 00:06:15,815 --> 00:06:17,865 We need another blank line here too. 120 00:06:17,865 --> 00:06:18,945 There we go. 121 00:06:18,945 --> 00:06:20,845 Okay, so there's our username field. 122 00:06:21,845 --> 00:06:22,645 Well, what comes next? 123 00:06:22,645 --> 00:06:26,755 Well, in the register, we ask for a username, we ask for an email address, and 124 00:06:26,755 --> 00:06:28,940 we ask for a password twice. 125 00:06:28,940 --> 00:06:31,690 So let's figure out how to do the email. 126 00:06:31,690 --> 00:06:35,190 Email, and this is a StringField. 127 00:06:36,860 --> 00:06:40,810 And we're gonna say Email, right? 128 00:06:40,810 --> 00:06:42,330 Now what validators does email have? 129 00:06:44,510 --> 00:06:45,560 It's got a few. 130 00:06:45,560 --> 00:06:49,740 So the first one that it has, is that data is required. 131 00:06:50,790 --> 00:06:52,410 Something has to be there. 132 00:06:52,410 --> 00:06:54,470 Second one is email. 133 00:06:54,470 --> 00:06:56,070 It has to be email. 134 00:06:56,070 --> 00:06:59,725 And then lastly, we're gonna make another function named email_exists. 135 00:07:01,750 --> 00:07:03,420 So let's go do those two. 136 00:07:03,420 --> 00:07:08,837 So, first of all we need to import email. 137 00:07:08,837 --> 00:07:09,475 And you know what? 138 00:07:09,475 --> 00:07:14,200 That's 76 characters, I bet we're gonna have to import something else, so 139 00:07:14,200 --> 00:07:16,244 let's put a parenthesis there and 140 00:07:16,244 --> 00:07:19,717 then that way we can import some stuff on the next line too. 141 00:07:19,717 --> 00:07:25,880 So name exists is already there, so let's add def email_exists and 142 00:07:25,880 --> 00:07:29,394 again, form, field, you know what? 143 00:07:29,394 --> 00:07:32,832 Let's take this whole thing. 144 00:07:32,832 --> 00:07:36,431 And paste it, because these two are gonna be almost identical, except for 145 00:07:36,431 --> 00:07:37,698 you want this to be email. 146 00:07:37,698 --> 00:07:39,120 And you want this to be email. 147 00:07:42,140 --> 00:07:46,840 So, same idea on these two, name exists, email exists, except for 148 00:07:46,840 --> 00:07:48,360 what they point to. 149 00:07:48,360 --> 00:07:51,300 All right, so there's that one. 150 00:07:51,300 --> 00:07:52,970 And let's add in password. 151 00:07:52,970 --> 00:07:55,350 This is gonna be a password field. 152 00:07:55,350 --> 00:07:56,190 Oh, new import. 153 00:07:56,190 --> 00:07:58,990 And we'll say Password. 154 00:07:58,990 --> 00:08:00,700 And it's gonna have some validators too. 155 00:08:02,330 --> 00:08:04,925 Validators are gonna be DataRequired. 156 00:08:05,990 --> 00:08:07,190 Cuz they have to give us a password. 157 00:08:08,320 --> 00:08:12,520 Link and we'll say it has to be at least two characters long for the password. 158 00:08:12,520 --> 00:08:16,040 I would really say you make this like seven or eight or something, but 159 00:08:16,040 --> 00:08:17,530 it has to be at least two. 160 00:08:17,530 --> 00:08:20,200 And then, we're gonna do a new one here. 161 00:08:20,200 --> 00:08:21,330 EqualTo. And 162 00:08:21,330 --> 00:08:25,230 we're gonna say it's gonna be EqualTo password2, whatever that is. 163 00:08:25,230 --> 00:08:30,470 And then the message if it doesn't match is gonna be, Passwords must match. 164 00:08:32,260 --> 00:08:33,340 Okay. 165 00:08:33,340 --> 00:08:35,550 Close our validators, close our password field. 166 00:08:35,550 --> 00:08:39,490 And let's actually go ahead and write password2 before we go add our import. 167 00:08:39,490 --> 00:08:44,355 So password2, hey there it is, is also a PasswordField. 168 00:08:45,630 --> 00:08:49,540 And it will say Confirm Password. 169 00:08:50,610 --> 00:08:54,498 And really we could leave this one completely alone, but 170 00:08:54,498 --> 00:08:58,150 I do wanna add one validator to this of DataRequired. 171 00:09:00,180 --> 00:09:03,840 Just to make sure that they fill in some data on that. 172 00:09:05,280 --> 00:09:09,640 So we need to import PasswordField from wtforms. 173 00:09:09,640 --> 00:09:13,810 And we need to import Length and EqualTo from our validator. 174 00:09:15,440 --> 00:09:18,470 So StringField PasswordField. 175 00:09:18,470 --> 00:09:22,020 And then here, we're gonna say Length and EqualTo. 176 00:09:23,500 --> 00:09:25,020 And that is our form. 177 00:09:26,070 --> 00:09:29,212 That is a long, long form. 178 00:09:29,212 --> 00:09:29,842 Wow! 179 00:09:29,842 --> 00:09:31,545 There's a lot to building a form, 180 00:09:31,545 --> 00:09:34,318 at least one that does all the stuff that we want it to do. 181 00:09:34,318 --> 00:09:38,305 Knowing how to customize validation will go a long way when you start building your 182 00:09:38,305 --> 00:09:39,359 own forms, though.