Forms

View saved

Goal for this part

Let visitors draft a new post through an HTML form that is validated on the server. Valid drafts appear on the home page; invalid ones show clear field errors instead of failing silently.

Posts are still stored in an in-memory list for this snapshot. That keeps the focus on forms, CSRF protection, redirects, and flash messages before you introduce a real database.

What you should already know

You should understand routes and render_template from the Templates part. Knowing that HTTP has GET (read a page) and POST (submit data) will make the form handler easier to follow.

Classes in Python appear here as form definitions. If classes feel rusty, the linked Python lesson is enough background for this teaching style.

Concepts: Flask-WTF, validation, and CSRF

Flask-WTF wraps WTForms so you can declare fields and validators in Python. Validators such as DataRequired and Length reject empty or oversized input before you trust the values.

Cross-Site Request Forgery (CSRF) protection needs a SECRET_KEY so Flask can sign tokens. In the template, form.hidden_tag() includes the CSRF field. Skipping it is a common reason “nothing happens” on submit.

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField
from wtforms.validators import DataRequired, Length

class PostForm(FlaskForm):
    title = StringField("Title", validators=[DataRequired(), Length(max=120)])
    content = TextAreaField("Content", validators=[DataRequired(), Length(min=10)])
    submit = SubmitField("Publish draft")

Walkthrough: handle GET and POST

The /new route accepts both GET and POST. On GET you show an empty form. On POST, validate_on_submit() is true only when the request method is POST and every validator passes.

On success, insert a dictionary into the front of POSTS, flash a success message, and redirect home. Redirect-after-POST avoids duplicate submits when someone refreshes. On failure, re-render the same template so errors can appear next to the fields.

@app.route("/new", methods=["GET", "POST"])
def new_post():
    form = PostForm()
    if form.validate_on_submit():
        POSTS.insert(0, {
            "title": form.title.data,
            "author": "Guest",
            "body": form.content.data,
        })
        flash("Draft saved in memory (no database yet).", "success")
        return redirect(url_for("home"))
    return render_template("new_post.html", form=form)

Flash messages and how to run the snapshot

Flash messages are short notes stored in the session for the next request. In base.html, call get_flashed_messages(with_categories=true) so every page can show feedback after a redirect—not only the form page.

Run the 03-Forms snapshot with the usual venv commands. Open home, follow the link to create a draft, submit a valid title and body, and confirm you land on home with a success banner and your new post at the top.

git clone https://github.com/michaeldunga1/fcc-flask-blog.git
cd fcc-flask-blog/03-Forms
python3 -m venv .venv
source .venv/bin/activate
pip install -r ../requirements.txt
python app.py

Common mistakes and troubleshooting

If every submit fails with a CSRF error, check that SECRET_KEY is set and that the template includes form.hidden_tag(). If the form redisplays with no clear reason, look for field error markup in the template—validators may be failing on length rules.

Remember that in-memory posts disappear when you stop the server. That is expected in this part and motivates the database snapshot next.

Try this checklist

  • Submit an empty form and confirm field errors appear
  • Submit a title that is too long and read the validation message
  • Publish a valid draft and confirm the flash message on home
  • Restart the server and notice in-memory posts are gone

Next: Database

Comments

One comment per signed-in account. Comments are saved with this page’s URL.