UNDER DEVELOPMENT - SEE HERE: https://docs.google.com/document/d/1eaawSS0iBVJxzz6IdxCNxFqLe0nWcOvSzqB1cFaY620/edit?usp=sharing
Example here:
https://repl.it/@Mr_D/Flask-Movie-Database
Flask-WTF (yep, thats its real name) allows you to write forms tidily in your Flask code, then use them where-and-when you need them. It also provides integrity checks on your form data and enables you to modify and/or accept/reject data in fields as required ( form.validate_on_submit() ). All of this, nice and tidily in your forms.py file.
You could ignore Flask WTF and create your own forms using standard HTML, but Flask WTF takes care of a lot of the fluff like elegantly handling errors as well as CSRF and other security concerns - although something you need to investigate is malicious data being entered into forms - is it sanitised? Does it need to be? Could Javascript be entered into a field then would the site execute it? If so, how is this dealt with? And what the heck is CSRF? You'll probably want to investigate that further.
There are two forms examples in this code using the flask-wtf library. The first is to allow you to add new movies, the second is a dropdown-list that shows how you would use one, since they're a bit more complex than other fields. The imports are grouped logically, first the main WTForms library (FlaskForm) which allows you to create forms, then the fields the form will use (in this case IntegerField for the year, TextField for the name and TextAreaField (a multi-line text field) for the description. The IntegerField is a special case of the TextField where it takes string input and forces it to an int, in a similar way to using int(input()). Read more on the fields available for use here: https://wtforms.readthedocs.io/en/stable/fields.html#basic-fields
Lets take a look at the Add_Movie class in detail. The first thing to note is that the class inherits its base information from FlaskForm (the base class). This is a way of getting all the general things needed for forms to work that all forms would need (FlaskForm) in one class, then we make our form (Add_Movie) with its specific needs on top of that. In Object Oriented programming this is known as inheritance, because your new class will inherit all the features of the base class.
The main part of the form is where we declare the fields our form will have - so for the moment we'll jump over the check_year function and come back to it shortly. The fields are title, year and description and they are simply set to be the fields required. Each field is given a name which is used to identify it, and can be given validators as well. Validators are run on the form data when it is submitted.
The title field is given one validator, DataRequired() - this is like saying Not Null on a database field, and means if the user leaves it blank, it will throw an error on submission.
The year field is given two validators (note that the validators are specified in a list, even if there is only one). Optional() means what it says, that the field is not required to be filled in, and if left blank it will still pass validation. Next we'll look at the second validator, check_year.
The second validator in the year field, check_year, is a function name. It simply means find the function of that name and run it. You can see the function at the start of the class, with two parameters: form and field which are sent to the function automatically. form contains the form information, while field contains the data and other details specific to the field being validated. You can see here that the function uses the Python datetime library to find out the current year, then checks to see if field.data (the data entered by the user in this field) is greater that the current year. If it is, a ValidationError is raised along with a message, otherwise the function ends with no error being raised, meaning that specific validator has passed.
If any validator fails, then validate_on_submit(), a WTForms flag, is set to False. You'll see this further on.
NOTE: The validator functions do not need to be inside the class, this one is because it is only ever used in this form. If it was outside the class it could be used by other forms as well, which is sometimes useful.
So thats the forms.py file set up - now we use it in a route.
So here's a section-by-section breakdown of how it works in routes.py (called main.py in the example because thats what Repl.it needs it to be called). look at add_movie.html in repl.it while reading through this:
First of all you'll notice some extra stuff in the @app.route line below, this tells Flask that this route can not only provide info to the browser (GET - this is what happens when you look at a web page), it can accept info back from the web page (POST - this is what happens when, for example, you press submit on a form). And the first thing we do in the route function is 'instantiate an instance of the forms class, Add_Movie' - or in plain English we make a form, using the Add_Movie class as the blueprint for that.
The next thing to do is check if the browser looking at the site is doing a GET request, or POSTing data... in this case, we're writing some code to deal with the request (ie: someone just clicked on the link to the Add a Movie page). All we do here is render the template and send it the usual stuff, plus the form we made in the previous step.
So you see a form, which you can fill in like this. Note there is no elegance to this template at all - the fields should be named and there should be some style - but the three are name, year and description. Notice the red brackets - these are there because there is nothing to say don't show it if there are no errors. Also, in this instance a year way off in the future has been used... but in forms.py there is a validator which checks to make sure the year is in the past, so this will fail as we'll see later on.
Now we deal with the 'not a GET' scenario - ie: its a POST so someone has clicked submit on the Add a Movie page. This means the webpage is sending back the form with (hopefully) filled out fields.
Because we're expecting a correctly filled in form, and because WTF can check this for us, we call the built-in validator (we'll cover this later) to check if the fields are as expected.
If all the fields in the form pass the validation checks then we use models.py to create a new Movie instance called, in this case, new_movie (remember classes are just blueprints, so while the main use of models.py is to map between the database and the code for queries, we can also go the other way and make new ones for the database... this is how).
This new_movie object (instances of a class, like new_movie, are called objects) can now have its fields filled in (it won't have any information in it before this, because its brand new). We simply take the three fields in the form and get the data from each, putting it in the correct place in new_movie.
Now we add this to the database, then commit it. this is a bit like GIT in that you can add files, but then need to commit them to make them stick, although no commit message is required here (although a comment might be useful).
As soon as the new_movie object is committed to the database, it gets an id, just the same as when you click commit in SQLiteStudio and the id field automagically fills itself in. This means that now you can access that, so we can call a redirect in Flask to the movie details page using that id (new_movie.id).
redirect uses url_for to find the function called details and uses the id of the newly added movie
Then we can deal with what happens if the form doesn't validate. In this case, we just go back to the page, passing the form back again - this time the form contains 2 main things now:
1. the data entered by the user the first time around
2. the errors which form.validate_on_submit() found
So the page will reload, with the previously entered information and some red (see the template)
And here is what it would return to the user:
Hopefully that is helpful in explaining the basics of a form. You'll notice there is another form with a dropdown list in it - these are handy, but a little awkward in how the dropdown gets populated with items - so the example is there to show you how to do one. Any questions, just ask. You'll need to pip install flask_wtf and use the correct imports in the right places to get this working in your project of course.