diff --git a/README.md b/README.md index cc852f0..4d17f1a 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,9 @@ $ python app.py ``` Visit [http://localhost:5000/](http://localhost:5000/) in your browser to see the results. + +### Running Tests + +``` +python test_models.py +``` diff --git a/desserts.db b/desserts.db new file mode 100644 index 0000000..b960a6d Binary files /dev/null and b/desserts.db differ diff --git a/models.py b/models.py index e40d530..e23fba1 100644 --- a/models.py +++ b/models.py @@ -1,4 +1,5 @@ from app import db +from flask import session class Dessert(db.Model): @@ -13,10 +14,14 @@ class Dessert(db.Model): price = db.Column(db.Float) calories = db.Column(db.Integer) - def __init__(self, name, price, calories): + user_id = db.Column(db.Integer, db.ForeignKey('user.id')) + user = db.relationship("User", backref="desserts") + + def __init__(self, name, price, calories, user_id): self.name = name self.price = price self.calories = calories + self.user_id = user_id def calories_per_dollar(self): if self.calories: @@ -32,20 +37,197 @@ def __init__(self, name): self.name = name -def create_dessert(new_name, new_price, new_calories): +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(100)) + password = db.Column(db.String(100)) + email = db.Column(db.String(250)) + name = db.Column(db.String(100)) + avatar = db.Column(db.String(250)) + + def __init__(self, username, password, email, name, avatar): + self.username = username + self.password = password + self.email = email + self.name = name + self.avatar = avatar + + +def get_password(username): + user = User.query.filter_by(username=username).first() + return user.password + + +def list_users(): + return User.query.all() + + +def get_user(id): + return User.query.get(id) + + +def get_user_by_username(username): + return User.query.filter_by(username=username).first() + + +def create_user(username, email, password, realname, avatar): + user = User(username, email, password, realname, avatar) + db.session.add(user) + db.session.commit() + return user + + +def update_user(id, username=None, email=None, password=None, realname=None, avatar=None): + + user = User.query.get(id) + + if username: + user.username = username + + if email: + user.email = email + + if password: + user.password = password + + if realname: + user.realname = realname + + if avatar: + user.avatar = avatar + + db.session.commit() + return user + + +def login(username, password): + # check if user exists in database + user = get_user_by_username(username) + if user is None: + raise Exception("Sorry, we don't have your username on file.") + + # check if password is correct + db_password = get_password(username) + if password != db_password: + raise Exception("Incorrect password.") + + # store username in Flask session + session["username"] = username + + +def get_logged_in_user(): + + # get stored username from Flask session + current_user = session["username"] + + # get user object + user = get_user_by_username(current_user) + + # return user object + return user + + +def get_desserts_by_user(username): + + # get user object + user = get_user_by_username(username) + + # query all desserts matching id of user object + desserts = Dessert.query.filter_by(user_id=user.id).all() + return desserts + + +def edit_dessert(user, new_name, new_price, new_calories): + + # we need every piece of input to be provided + if new_name is None or new_price is None or new_calories is None: + raise Exception("Need name, price, and calories!") + + if new_name == '' or new_price == '' or new_calories == '': + raise Exception("Need name, price, and calories!") + + # check reasonable number of calories + if int(new_calories) < 100: + raise Exception("That's not enough calories!") + elif int(new_calories) > 3000: + raise Exception("That's too many calories!") + + # get dessert from database + dessert = Dessert.query.filter_by(name=new_name).first() + + # users can only edit their own desserts + if user.id != dessert.user_id: + raise Exception("That's not your dessert!") + + dessert.price = new_price + dessert.calories = new_calories + db.session.commit() + + +def create_dessert(new_name, new_price, new_calories, new_user_id): # Create a dessert with the provided input. - # At first, we will trust the user. - # This line maps to line 16 above (the Dessert.__init__ method) - dessert = Dessert(new_name, new_price, new_calories) + # We need every piece of input to be provided. + + # Can you think of other ways to write this following check? + if new_name is None or new_price is None or new_calories is None or new_user_id is None: + raise Exception("Need name, price, calories, and user!") + + # They can also be empty strings if submitted from a form + if new_name == '' or new_price == '' or new_calories == '' or new_user_id == '': + raise Exception("Need name, price, calories, and user!") + + # check for duplicate items + name_check = Dessert.query.filter_by(name=new_name).first() + if name_check: + if new_name == name_check.name: + raise Exception("That dessert already exists!") + # check reasonable number of calories + if int(new_calories) < 100: + raise Exception("That's not enough calories!") + elif int(new_calories) > 3000: + raise Exception("That's too many calories!") + + # This line maps to line 16 above (the Dessert.__init__ method) + dessert = Dessert(new_name, new_price, new_calories, new_user_id) # Actually add this dessert to the database db.session.add(dessert) # Save all pending changes to the database - db.session.commit() - return dessert + try: + db.session.commit() + return dessert + except: + # If something went wrong, explicitly roll back the database + db.session.rollback() + + +def delete_dessert(user, id): + + # get dessert from database + dessert = Dessert.query.get(id) + + # users can only delete desserts they created + if user.id != dessert.user_id: + raise Exception("That's not your dessert!") + + if dessert: + # We store the name before deleting it, because we can't access it + # afterwards. + dessert_name = dessert.name + db.session.delete(dessert) + + try: + db.session.commit() + return "Dessert {} deleted".format(dessert_name) + except: + # If something went wrong, explicitly roll back the database + db.session.rollback() + return "Something went wrong" + else: + return "Dessert not found" if __name__ == "__main__": diff --git a/templates/add.html b/templates/add.html index e2d174a..7dd1883 100644 --- a/templates/add.html +++ b/templates/add.html @@ -1,27 +1,82 @@ -

Add Dessert

+{% include 'header.html' %} -{% if dessert %} + -

{{dessert.name}} successfully created! Add another below.

-

Back to Dessert List

+
-{% endif %} +
+ {% if user %} + Welcome {{ user }} | Logout | Home - -
+ {% else %} - - + Login | Home - - + {% endif %} +
- - +

Add Dessert

+ {% if dessert %} - - + - \ No newline at end of file + + + {% endif %} + + {% if error %} + + {% endif %} + + + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ + + + + + + +
+ + +
+ + \ No newline at end of file diff --git a/templates/details.html b/templates/details.html new file mode 100644 index 0000000..70e4023 --- /dev/null +++ b/templates/details.html @@ -0,0 +1,62 @@ +{% include 'header.html' %} + + + + + +
+ +
+ {% if user %} + + Welcome {{ user }} | Logout | Home + + {% else %} + + Login | Home + + {% endif %} +
+ +

Dessert Details

+ + {% if error %} + + {% endif %} + +

{{ dessert.name }}

+ +

Price: ${{ dessert.price }}

+ +

Calories: {{ dessert.calories }}

+ +

Calories per dollar: {{ dessert.calories_per_dollar() }}

+ + Edit {{ dessert.name }} + + + Delete {{ dessert.name }} + + + + + + \ No newline at end of file diff --git a/templates/edit.html b/templates/edit.html new file mode 100644 index 0000000..2d5664a --- /dev/null +++ b/templates/edit.html @@ -0,0 +1,72 @@ +{% include 'header.html' %} + + + +
+ +
+ {% if user %} + + Welcome {{ user }} | Logout | Home + + {% else %} + + Login | Home + + {% endif %} +
+ +

Edit Dessert

+ + + {% if error %} + + {% endif %} + + + + + +
+ +
+ + + + + + +
+ +
+ + + + +
+ +
+ + + + +
+ + + + + + + +
+ + +
+ + \ No newline at end of file diff --git a/templates/header.html b/templates/header.html new file mode 100644 index 0000000..2f60415 --- /dev/null +++ b/templates/header.html @@ -0,0 +1,21 @@ + + + Desserts + + + + + + + + + + + + + + + + + + diff --git a/templates/index.html b/templates/index.html index 8220939..aadea61 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,9 +1,51 @@ -

Dessert Menu

+{% include 'header.html' %} - + -Add Item \ No newline at end of file +
+ +
+ {% if user %} + + Welcome {{ user }} | Logout | Home + + {% else %} + + Login | Home + + {% endif %} +
+ + +

Log In

+ + {% if error %} + + {% endif %} + + +
+ +
+ + + + +
+ +
+ + + + +
+ + + +
+ + \ No newline at end of file diff --git a/templates/menu.html b/templates/menu.html new file mode 100644 index 0000000..e15c62b --- /dev/null +++ b/templates/menu.html @@ -0,0 +1,42 @@ +{% include 'header.html' %} + + + +
+ + + {% if user %} +
+ Welcome {{ user }} | Logout | Home +
+

{{ user }}'s Dessert Menu

+ + {% else %} +
+ Login | Home +
+

Dessert Menu

+ + {% endif %} + + {% if message %} + + {% endif %} + + +
    + {% for dessert in desserts %} +
  • {{ dessert.name }} - ${{ dessert.price }}, {{ dessert.calories }} calories
  • + {% endfor %} +
+ + Add Item + + +
+ + \ No newline at end of file diff --git a/views.py b/views.py index 4e42ae5..0878751 100644 --- a/views.py +++ b/views.py @@ -1,22 +1,95 @@ -from flask import render_template, request +from flask import render_template, request, session -from models import Dessert, create_dessert +from models import Dessert, create_dessert, delete_dessert, edit_dessert, login, get_logged_in_user from app import app -@app.route('/') +@app.route('/', methods=["GET", "POST"]) def index(): - desserts = Dessert.query.all() + if request.method == 'GET': + # if user is logged in, show only their desserts + if "username" in session: + user = get_logged_in_user() + desserts = Dessert.query.filter_by(user_id=user.id).all() + return render_template("menu.html", user=user.name, desserts=desserts) + # if no user is logged in, show all desserts + else: + desserts = Dessert.query.all() + return render_template("menu.html", user=None, desserts=desserts) + + # post method is user attempting to log in + # get username and password from posted login form + username = request.form.get('username_field') + password = request.form.get('password_field') + + try: + # login() from models.py + login(username, password) + except Exception as e: + return render_template("index.html", error=e.message) + + # once logged in, show only user's desserts + user = get_logged_in_user() + desserts = Dessert.query.filter_by(user_id=user.id).all() + return render_template("menu.html", user=user.name, desserts=desserts) + + +@app.route("/login") +def login_user(): + return render_template("index.html") + + +@app.route("/menu") +def menu(): + user = get_logged_in_user() - return render_template('index.html', desserts=desserts) + desserts = Dessert.query.filter_by(user_id=user.id).all() + return render_template('menu.html', user=user.name, desserts=desserts) + + +@app.route("/logout") +def logout(): + session.pop('username',None) + return index() + +@app.route('/edit/', methods=['GET', 'POST']) +def edit(id): + + dessert = Dessert.query.get(id) + + if request.method == 'GET': + if "username" in session: + user = get_logged_in_user() + return render_template('edit.html', user=user.name, dessert=dessert) + else: + return render_template('details.html', user=None, dessert=dessert, error="You must be logged in to edit a dessert.") + + user = get_logged_in_user() + + dessert_name = dessert.name + dessert_price = request.form.get('price_field') + dessert_cals = request.form.get('cals_field') + + try: + dessert = edit_dessert(user, dessert_name, dessert_price, dessert_cals) + desserts = Dessert.query.filter_by(user_id=user.id).all() + return render_template('menu.html', user=user.name, desserts=desserts) + except Exception as e: + return render_template('edit.html', user=user.name, dessert=dessert, error=e.message) + @app.route('/add', methods=['GET', 'POST']) def add(): if request.method == 'GET': - return render_template('add.html') + if "username" in session: + user = get_logged_in_user() + return render_template('add.html', user=user.name) + else: + desserts = Dessert.query.all() + return render_template("menu.html", user=None, desserts=desserts, message="You must be logged in to add a dessert.") # Because we 'returned' for a 'GET', if we get to this next bit, we must # have received a POST @@ -25,9 +98,46 @@ def add(): # The values on the right, inside get(), correspond to the 'name' # values in the HTML form that was submitted. + user = get_logged_in_user() dessert_name = request.form.get('name_field') dessert_price = request.form.get('price_field') dessert_cals = request.form.get('cals_field') + + # Now we are checking the input in create_dessert, we need to handle + # the Exception that might happen here. + + # Wrap the thing we're trying to do in a 'try' block: + try: + dessert = create_dessert(dessert_name, dessert_price, dessert_cals, user.id) + return render_template('add.html', user=user.name, dessert=dessert) + except Exception as e: + # Oh no, something went wrong! + # We can access the error message via e.message: + return render_template('add.html', error=e.message) + + +@app.route('/desserts/') +def view_dessert(id): + + dessert = Dessert.query.get(id) + + if "username" in session: + user = get_logged_in_user() + return render_template('details.html', user=user.name, dessert=dessert) + else: + return render_template('details.html', user=None, dessert=dessert) + + +@app.route('/delete/') +def delete(id): + + if "username" in session: + user = get_logged_in_user() + message = delete_dessert(user, id) + return index() + else: + dessert = Dessert.query.get(id) + return render_template('details.html', user=None, dessert=dessert, error="You must be logged in to delete a dessert.") + - dessert = create_dessert(dessert_name, dessert_price, dessert_cals) - return render_template('add.html', dessert=dessert) +app.secret_key = "desserts_app!"