def login(): """ Checks inputs from the login form CHECKS: - username exists in database - hashed password matches database password hash SUCCESS: - logs in user - stores their id in a cookie - redirects to the index FAILURE: - tracks errors and passes to "flash" - renders the login HTML """ if request.method == "POST": username = request.form["username"] password = request.form["password"] db = get_db() error = None # Query the database for a user and return all columns # User is unique (primary key), so a single row is returned user = db.execute("SELECT * FROM user WHERE username = ?", (username, )).fetchone() if user is None: error = "Incorrect username." elif not check_password_hash(user["password"], password): error = "Incorrect password." if error is None: # session is a dictionary that stores data across requests # When validation succeeds, the user's `id` is stored in # a new session as a cookie. Flask securely signs # cookies so they can't be tampered with session.clear() session["user_id"] = user["id"] return redirect(url_for("index")) # Now that the user's `id` is stored in the session, # it will be available on subsequent requests. # At the beginning of each request, if a user is logged in # their information should be loaded and available to # views flash(error) return render_template("auth/login.html")
def load_logged_in_user(): """ Checks inputs from the login form CHECKS: - user_id is stored as a cookie in the session SUCCESS: - queries the database for the user_id - stores the user data in g.user FAILURE: - g.user is set to None """ user_id = session.get("user_id") if user_id is None: g.user = None else: g.user = get_db().execute("SELECT * FROM user WHERE id = ?", (user_id, )).fetchone()
def create(): if request.method == "POST": title = request.form["title"] body = request.form["body"] error = None if not title: error = "Title is required." if error is not None: flash(error) else: db = get_db() db.execute( "INSERT INTO post (title, body, author_id)" + " VALUES (?, ?, ?)", (title, body, g.user["id"])) db.commit() return redirect(url_for("blog.index")) return render_template("blog/create.html")
def update(id): post = get_post(id) if request.method == "POST": title = request.form["title"] body = request.form["body"] error = None if not title: error = "Title is required." if error is not None: flash(error) else: db = get_db() db.execute("UPDATE post SET title = ?, body = ?" + " WHERE id = ?", (title, body, id)) db.commit() return redirect(url_for("blog.index")) return render_template("blog/update.html", post=post)
def get_post(id, check_author=True): post = get_db().execute( "SELECT p.id, title, body, created, author_id, username" + " FROM post p JOIN user u ON p.author_id = u.id" + " WHERE p.id = ?", (id, )).fetchone() if post is None: # abort() will raise a special exception that returns a # HTTP status code. It takes an optional message to # show with the error, otherwise a default message is used # 404 == 'Not Found' # 403 = 'Forbidden' abort(404, f"Post id {id} doesn't exist.") # the `check_author` argument is defined so that the function # can be used to get a `post` without checking the author # This would be helpful if you wrote a view to show an individual # post on a page, where the user doesn't matter because they're # not modifying the post. if check_author and post["author_id"] != g.user["id"]: abort(403) return post
def register(): """ Checks inputs from the login form CHECKS: - username has a value - password has a value - username is new (does not exist in database) SUCCESS: - user added to database - database saved (commit) - redirects to the login FAILURE: - tracks errors and passes to "flash" - renders the register HTML """ if request.method == "POST": # `request.form` is a special dictionary that maps form # keys and their values username = request.form["username"] password = request.form["password"] db = get_db() error = None if not username: error = "Username is required." elif not password: error = "Password is required." # `db.execute` # `fetchone()` returns one row from the query or None # Alternatively, `fetchall()` returns a list of results # ******************************************************** # NOTE: NEVER add variables directly to the SQL statement # with string formatting. This makes it possible to attack # the application with SQL Injection # ******************************************************** elif db.execute("SELECT id FROM user WHERE username = ?", (username, )).fetchone() is not None: error = f"User {username} is already registered." if error is None: db.execute( "INSERT INTO user (username, password) VALUES (?, ?)", # Password is hashed for security (username, generate_password_hash(password))) # This query modifies data (INSERT), so we need to save # the change to the database db.commit() # `url_for()` generates the URL for the login view based # on its name. This is preferable to hardcoding the URL # `redirect` generates a redirect response to the URL return redirect(url_for("auth.login")) # `flash()` stores messages that can be retrieved when # rendering the template (HTML) flash(error) # `render_template()` renders a template containing the HTML return render_template("auth/register.html")
def index(): db = get_db() posts = db.execute("SELECT p.id, title, body, created, author_id, username" " FROM post p JOIN user u ON p.author_id = u.id" " ORDER BY created DESC").fetchall() return render_template("blog/index.html", posts=posts)
def delete(id): get_post(id) db = get_db() db.execute("DELETE FROM post WHERE id = ?", (id, )) db.commit() return redirect(url_for("blog.index"))