-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Danny Denenberg
authored and
Danny Denenberg
committed
Oct 28, 2021
1 parent
89ceba2
commit 0cf1780
Showing
14 changed files
with
557 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Roar | ||
A tweeting app (clone) made with [Flask](https://flask.palletsprojects.com/en/2.0.x/). | ||
|
||
When adding to git, do NOT include `venv` and `__pycache__`. Run `pip freeze > requirements.txt` to get all of your dependencies into a requirements file and DO include that. | ||
|
||
The `@click.command("init-db")` decorator makes a function call when you type: `flask init-db`. Do this to initialize the database for the first time (it wipes previous data). | ||
|
||
### Running | ||
|
||
1. `cd` into the main directory. | ||
2. Create and initialize a virtual enviornment with `python3 -m venv venv` and `. venv/bin/activate`. | ||
3. Install the dependencies with `pip install -r requirements.txt`. | ||
4. If you are running the program for the first time and don't have the database set up, run `flask init-db`. | ||
5. Run the program! `flask run`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,53 @@ | ||
from flask import Flask, g | ||
import sqlite3 | ||
import os | ||
|
||
app = Flask(__name__) | ||
from flask import Flask | ||
|
||
def connect_db(): | ||
sql = sqlite3.connect('./database.db') | ||
sql.row_factory = sqlite3.Row | ||
return sql | ||
|
||
def create_app(test_config=None): | ||
"""Create and configure an instance of the Flask application.""" | ||
app = Flask(__name__, instance_relative_config=True) | ||
app.config.from_mapping( | ||
# a default secret that should be overridden by instance config | ||
SECRET_KEY="dev", | ||
# store the database in the instance folder | ||
DATABASE=os.path.join(app.instance_path, "password_manager.sqlite"), | ||
) | ||
|
||
def get_db(): | ||
#Check if DB is there | ||
if not hasattr(g, 'sqlite3'): | ||
g.sqlite3_db = connect_db() | ||
return g.sqlite3_db | ||
if test_config is None: | ||
# load the instance config, if it exists, when not testing | ||
app.config.from_pyfile("config.py", silent=True) | ||
else: | ||
# load the test config if passed in | ||
app.config.update(test_config) | ||
|
||
#close the connection to the database automatically | ||
@app.teardown_appcontext | ||
def close_db(error): | ||
#if global object has a sqlite database then close it. If u leave it open noone can access it and gets lost in memory causing leaks. | ||
if hasattr(g, 'sqlite_db'): | ||
g.sqlite3_db.close() | ||
# ensure the instance folder exists | ||
try: | ||
os.makedirs(app.instance_path) | ||
except OSError: | ||
pass | ||
|
||
@app.route('/') | ||
def index(): | ||
return f'<h1>Hello, World!</h1>' | ||
@app.route("/hello") | ||
def hello(): | ||
return "Hello, World!" | ||
|
||
@app.route('/users') | ||
def viewusers(): | ||
db = get_db() | ||
cursor = db.execute('select id, name, age from people') | ||
results = cursor.fetchall() | ||
return f"<h1>The Id is {results[0]['id']}.<br> The Name is {results[0]['name']}. <br> The age is {results[0]['age']}. </h1>" | ||
# register the database commands | ||
import db | ||
|
||
db.init_app(app) | ||
|
||
if __name__ == '__main__': | ||
app.run(debug = True) | ||
# apply the blueprints to the app | ||
import auth, blog | ||
|
||
app.register_blueprint(auth.bp) | ||
app.register_blueprint(blog.bp) | ||
|
||
# make url_for('index') == url_for('blog.index') | ||
# in another app, you might define a separate main index here with | ||
# app.route, while giving the blog blueprint a url_prefix, but for | ||
# the tutorial the blog will be the main index | ||
app.add_url_rule("/", endpoint="index") | ||
|
||
return app | ||
|
||
|
||
create_app() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import functools | ||
|
||
from flask import Blueprint | ||
from flask import flash | ||
from flask import g | ||
from flask import redirect | ||
from flask import render_template | ||
from flask import request | ||
from flask import session | ||
from flask import url_for | ||
from werkzeug.security import check_password_hash | ||
from werkzeug.security import generate_password_hash | ||
|
||
from db import get_db | ||
|
||
bp = Blueprint("auth", __name__, url_prefix="/auth") | ||
|
||
|
||
def login_required(view): | ||
"""View decorator that redirects anonymous users to the login page.""" | ||
|
||
@functools.wraps(view) | ||
def wrapped_view(**kwargs): | ||
if g.user is None: | ||
return redirect(url_for("auth.login")) | ||
|
||
return view(**kwargs) | ||
|
||
return wrapped_view | ||
|
||
|
||
@bp.before_app_request | ||
def load_logged_in_user(): | ||
"""If a user id is stored in the session, load the user object from | ||
the database into ``g.user``.""" | ||
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() | ||
) | ||
|
||
|
||
@bp.route("/register", methods=("GET", "POST")) | ||
def register(): | ||
"""Register a new user. | ||
Validates that the username is not already taken. Hashes the | ||
password for security. | ||
""" | ||
if request.method == "POST": | ||
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." | ||
|
||
if error is None: | ||
try: | ||
db.execute( | ||
"INSERT INTO user (username, password) VALUES (?, ?)", | ||
(username, generate_password_hash(password)), | ||
) | ||
db.commit() | ||
except db.IntegrityError: | ||
# The username was already taken, which caused the | ||
# commit to fail. Show a validation error. | ||
error = f"User {username} is already registered." | ||
else: | ||
# Success, go to the login page. | ||
return redirect(url_for("auth.login")) | ||
|
||
flash(error) | ||
|
||
return render_template("auth/register.html") | ||
|
||
|
||
@bp.route("/login", methods=("GET", "POST")) | ||
def login(): | ||
"""Log in a registered user by adding the user id to the session.""" | ||
if request.method == "POST": | ||
username = request.form["username"] | ||
password = request.form["password"] | ||
db = get_db() | ||
error = None | ||
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: | ||
# store the user id in a new session and return to the index | ||
session.clear() | ||
session["user_id"] = user["id"] | ||
return redirect(url_for("index")) | ||
|
||
flash(error) | ||
|
||
return render_template("auth/login.html") | ||
|
||
|
||
@bp.route("/logout") | ||
def logout(): | ||
"""Clear the current session, including the stored user id.""" | ||
session.clear() | ||
return redirect(url_for("index")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
from flask import Blueprint | ||
from flask import flash | ||
from flask import g | ||
from flask import redirect | ||
from flask import render_template | ||
from flask import request | ||
from flask import url_for | ||
from werkzeug.exceptions import abort | ||
|
||
from auth import login_required | ||
from db import get_db | ||
|
||
bp = Blueprint("blog", __name__) | ||
|
||
|
||
@bp.route("/") | ||
def index(): | ||
"""Show all the posts, most recent first.""" | ||
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 get_post(id, check_author=True): | ||
"""Get a post and its author by id. | ||
Checks that the id exists and optionally that the current user is | ||
the author. | ||
:param id: id of post to get | ||
:param check_author: require the current user to be the author | ||
:return: the post with author information | ||
:raise 404: if a post with the given id doesn't exist | ||
:raise 403: if the current user isn't the author | ||
""" | ||
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(404, f"Post id {id} doesn't exist.") | ||
|
||
if check_author and post["author_id"] != g.user["id"]: | ||
abort(403) | ||
|
||
return post | ||
|
||
|
||
@bp.route("/create", methods=("GET", "POST")) | ||
@login_required | ||
def create(): | ||
"""Create a new post for the current user.""" | ||
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") | ||
|
||
|
||
@bp.route("/<int:id>/update", methods=("GET", "POST")) | ||
@login_required | ||
def update(id): | ||
"""Update a post if the current user is the author.""" | ||
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) | ||
|
||
|
||
@bp.route("/<int:id>/delete", methods=("POST",)) | ||
@login_required | ||
def delete(id): | ||
"""Delete a post. | ||
Ensures that the post exists and that the logged in user is the | ||
author of the post. | ||
""" | ||
get_post(id) | ||
db = get_db() | ||
db.execute("DELETE FROM post WHERE id = ?", (id,)) | ||
db.commit() | ||
return redirect(url_for("blog.index")) |
Empty file.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.