diff --git a/app.py b/app.py index 4347b45..a1190e6 100644 --- a/app.py +++ b/app.py @@ -2,9 +2,11 @@ from flask.ext.sqlalchemy import SQLAlchemy app = Flask(__name__) +app.secret_key = 'thisisasecret' #To "log in" a user, first make sure you have imported the session - you'll also need to set up an app secret key. -# Set up the SQLAlchemy Database to be a local file 'desserts.db' -app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///desserts.db' + +# Set up the SQLAlchemy Database to be a local file 'users.db' +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db' db = SQLAlchemy(app) diff --git a/desserts.db b/desserts.db new file mode 100644 index 0000000..7c69d21 Binary files /dev/null and b/desserts.db differ diff --git a/models.py b/models.py index 65eaf43..2020588 100644 --- a/models.py +++ b/models.py @@ -1,4 +1,5 @@ from app import db +from users import get_user_by_username class Dessert(db.Model): @@ -12,20 +13,23 @@ class Dessert(db.Model): name = db.Column(db.String(100)) price = db.Column(db.Float) calories = db.Column(db.Integer) - + origin = db.Column(db.String(100)) + image_url = db.Column(db.String(100)) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user = db.relationship("User", backref="desserts") - def __init__(self, name, price, calories): + def __init__(self, name, price, calories, origin,image_url,user_id): self.name = name self.price = price self.calories = calories + self.origin = origin + self.image_url = image_url + self.user_id = user_id def calories_per_dollar(self): if self.calories: return self.calories / self.price - class Menu(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -35,23 +39,36 @@ def __init__(self, name): self.name = name -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 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_desserts(user_id): + desserts = Dessert.query.filter_by(user_id=user_id).all() + return desserts + +def get_all_desserts(): + desserts = Dessert.query.all() + return desserts + + +def get_user_id(username): + user = get_user_by_username(username) + user_id = user.id + return user_id + +def create_dessert(new_name, new_price, new_calories, new_origin, new_image_url,new_user_id): # Create a dessert with the provided input. # We need every piece of input to be provided. @@ -64,8 +81,22 @@ def create_dessert(new_name, new_price, new_calories): if new_name == '' or new_price == '' or new_calories == '': raise Exception("Need name, price and calories!") + # Check for price + if int(new_price) > 50: + raise Exception("A bit too pricey!") + + # Check for calories + if int(new_calories) > 1000: + raise Exception("No one should eat that") + + #Check duplicates + if Dessert.query.filter_by(name=new_name).first(): + raise Exception("Already in the database") + + # This line maps to line 16 above (the Dessert.__init__ method) - dessert = Dessert(new_name, new_price, new_calories) + + dessert = Dessert(new_name, new_price, new_calories, new_origin, new_image_url,new_user_id) # Actually add this dessert to the database db.session.add(dessert) @@ -80,25 +111,68 @@ def create_dessert(new_name, new_price, new_calories): db.session.rollback() -def delete_dessert(id): +def edit_dessert(dessert, new_name, new_price, new_calories, new_origin, new_image_url): + # Edit a dessert with the provided input. - dessert = Dessert.query.get(id) - 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" + # 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: + raise Exception("Need name, price and calories!") + + # They can also be empty strings if submitted from a form + if new_name == '' or new_price == '' or new_calories == '': + raise Exception("Need name, price and calories!") + + # Check for price + if int(new_price) > 50: + raise Exception("A bit too pricey!") + + # Check for calories + if int(new_calories) > 1000: + raise Exception("No one should eat that") + + # This line maps to line 16 above (the Dessert.__init__ method) + dessert.name = new_name + dessert.price = new_price + dessert.calories = new_calories + dessert.origin = new_origin + dessert.image_url = new_image_url + + # Save all pending changes to the database + + try: + db.session.commit() + return dessert + except: + # If something went wrong, explicitly roll back the database + db.session.rollback() + +def delete_dessert(id,user_id): + + dessert = Dessert.query.get(id) + print dessert.user_id + print user_id + if dessert.user_id == user_id: + + 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" else: - return "Dessert not found" + return "You can't delete this dessert" + + if __name__ == "__main__": diff --git a/static/starter-template.css b/static/starter-template.css new file mode 100755 index 0000000..eab5c58 --- /dev/null +++ b/static/starter-template.css @@ -0,0 +1,10 @@ +body { + padding-top: 50px; +} +.starter-template { + padding: 40px 15px; + text-align: center; +} +.search { + align-items: baseline; +} diff --git a/templates/add.html b/templates/add.html index d3ad60c..e9781d8 100644 --- a/templates/add.html +++ b/templates/add.html @@ -1,6 +1,5 @@ {% include 'header.html' %} -
@@ -57,15 +56,31 @@

Add Dessert

+
+ + + + +
+ +
+ + + + +
+ + Back + - \ No newline at end of file + diff --git a/templates/details.html b/templates/details.html index 23f0356..cc4d219 100644 --- a/templates/details.html +++ b/templates/details.html @@ -1,23 +1,42 @@ {% include 'header.html' %} - +

Dessert Details

-

{{ dessert.name }}

+ {% if dessert %} + +

{{ dessert.name }}

+ +

Price: ${{ dessert.price }}

+ +

Calories: {{ dessert.calories }}

+ +

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

-

Price: ${{ dessert.price }}

+

Origin: {{ dessert.origin }}

-

Calories: {{ dessert.calories }}

+

Image:

+

-

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

+ Edit {{ dessert.name }} Delete {{ dessert.name }} + {% endif %} - Delete {{ dessert.name }} + {% if error %} + + {% endif %} + Back
- \ No newline at end of file + diff --git a/templates/edit.html b/templates/edit.html new file mode 100644 index 0000000..db6683a --- /dev/null +++ b/templates/edit.html @@ -0,0 +1,87 @@ +{% include 'header.html' %} + + +
+ + +

Edit Dessert

+ + {% if success %} + + + + + + {% endif %} + + {% if error %} + + {% endif %} + + {% if dessert %} + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + + + +
+ + + + + +
+

+ {% endif %} + + Back + +
+ + diff --git a/templates/header.html b/templates/header.html index afbc019..11287ee 100644 --- a/templates/header.html +++ b/templates/header.html @@ -1,6 +1,6 @@ - Desserts + Desserts for you! @@ -13,6 +13,55 @@ + + + + + + + + + + + diff --git a/templates/index.html b/templates/index.html index b6a05e5..2ef6076 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,30 +1,42 @@ {% include 'header.html' %} -
-

Dessert Menu

+

Dessert Menu

- {% if message %} - + {% endif %} + + + + + + + + {% for dessert in desserts %} + + + + + + + {% endfor %} +
Name Price Calories Image
{{dessert.name}}$ {{dessert.price}}{{dessert.calories}}
- +

Add Item

- Add Item
- \ No newline at end of file + + + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..a28bd4e --- /dev/null +++ b/templates/login.html @@ -0,0 +1,44 @@ +{% include 'header.html' %} + + +
+ +

Login

+ + {% if error %} + + {% endif %} + + + + + + +
+ +
+ + + + +
+ +
+ + +
+
+ +
+ +
+ Or Sign up. +
+ + diff --git a/templates/signup.html b/templates/signup.html new file mode 100644 index 0000000..61e9ec2 --- /dev/null +++ b/templates/signup.html @@ -0,0 +1,67 @@ +{% include 'header.html' %} + + +
+ +

Sign up

+ + {% if error %} + + {% endif %} + + + + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ + + + +
+ Or Login. +
+ + diff --git a/test_users.py b/test_users.py new file mode 100644 index 0000000..fb627fe --- /dev/null +++ b/test_users.py @@ -0,0 +1,80 @@ +""" Test file for models.py. + + When models.py is complete, this file should completely pass. + + NOTE: + To make these tests accurate, this file DELETES all content in the + database first. +""" +import traceback + +from users import * + + +def check_test(func): + """ This is a decorator that simply prints out whether the function + it calls succeeded or not. You don't need to edit this. + """ + def func_wrapper(*args, **kwargs): + try: + func(*args, **kwargs) + print ":) {} passed".format(func.__name__) + except AssertionError: + traceback.print_exc() + print ":( {} failed".format(func.__name__) + return func_wrapper + + +@check_test +def test_connection(): + # No longer needed, but let's update this to delete everything so we can + # test + User.query.delete() + db.session.commit() + + +@check_test +def test_create(): + # Test that the create user function finishes cleanly + result = create_user('test', 'test', 'test', 'test', 'test') + # assert is a special Python keyword that throws an error if the thing it + # is testing turns out not to be True. Our check_test decorator looks for + # errors and tells us the test failed if it found any errors like this. + assert result is not None + assert isinstance(result, User) + assert result.username == 'test' + + +@check_test +def test_create_worked(): + result = get_user_by_username('test') + assert result is not None + + +@check_test +def test_get_user_by_id(): + result = get_user(1) + assert result is not None + + +@check_test +def test_list_users(): + result = list_users() + assert isinstance(result, list) # isinstance says "is it of this type" + assert len(result) > 0 + + +@check_test +def test_update_user(): + result = update_user(1, password='newpass') + assert result is not None + assert isinstance(result, User) + assert result.password == 'newpass' + + +for item in dir(): + """ Loop through all the defined items we know about (functions, etc). + If the name starts with test_, assume it's a test function and run it! + """ + if item.startswith('test_'): + globals()[item]() diff --git a/users.db b/users.db new file mode 100644 index 0000000..cbba394 Binary files /dev/null and b/users.db differ diff --git a/users.py b/users.py new file mode 100644 index 0000000..015cce7 --- /dev/null +++ b/users.py @@ -0,0 +1,71 @@ +from app import db + + +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 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 get_user_by_email(email): + return User.query.filter_by(email=email).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): + # This one is harder with the object syntax actually! So we changed the + # function definition. + + 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 + + +if __name__ == "__main__": + db.create_all() diff --git a/views.py b/views.py index 679621a..fabe50c 100644 --- a/views.py +++ b/views.py @@ -1,60 +1,295 @@ -from flask import render_template, request +from flask import render_template, request, redirect, session, jsonify -from models import Dessert, create_dessert, delete_dessert +from models import * +from users import * from app import app @app.route('/') def index(): + if session.get('username'): # this will be executed if 'username' is present in the session + print "Logged-in: Found session" + username = session['username'] - desserts = Dessert.query.all() + user_id = get_user_id(username) - return render_template('index.html', desserts=desserts) + desserts = get_desserts(user_id) + + return render_template('index.html', desserts=desserts, username=username) + else: + print "Logged-in: No session" + return render_template('login.html', error="Please login.") + + +@app.route('/login') +def login(): + return render_template('login.html') + +@app.route('/submit-login', methods=['POST']) +def submit_login(): + #get form info + user_username = request.form.get('username_field') + user_password = request.form.get('password_field') + + print user_username + + # check for username + user_login = get_user_by_username(user_username) + if user_login: + # check for password + print "username is ok" + print user_login.password + print user_password + if user_login.password == user_password: + print "password is ok" + session['username'] = user_username + return redirect('/') + else: + print "password does not match" + return render_template('login.html', error="Login credentials don't not work") + else: + print "password does not match" + return render_template('login.html', error="Login credentials don't not work") + + +@app.route('/signup') +def signup_(): + return render_template('signup.html') + +@app.route('/submit-signup', methods=['POST']) +def submit_signup(): + #get form info + user_username = request.form.get('username_field') + user_password = request.form.get('password_field') + user_email = request.form.get('email_field') + user_name = request.form.get('name_field') + user_avatar = request.form.get('avatar_field') + + print user_username + + # check for duplicated username + if get_user_by_username(user_username): + return render_template('signup.html',error="Username already exists") + + # check for duplicated email + elif get_user_by_email(user_email): + return render_template('signup.html',error="Email already exists") + + # if no duplicates, create user + else: + try: + user = create_user(user_username, user_email, user_password, user_name, user_avatar) + session['username'] = user_username + return redirect('/') + except Exception as e: + # Oh no, something went wrong! + # We can access the error message via e.message: + return render_template('signup.html', error=e.message) @app.route('/add', methods=['GET', 'POST']) def add(): + if session.get('username'): # this will be executed if 'username' is present in the session + print "Logged-in: Found session" + username = session['username'] + + if request.method == 'GET': + return render_template('add.html') + + # Because we 'returned' for a 'GET', if we get to this next bit, we must + # have received a POST + + # Get the incoming data from the request.form dictionary. + # The values on the right, inside get(), correspond to the 'name' + # values in the HTML form that was submitted. + + dessert_name = request.form.get('name_field') + dessert_price = request.form.get('price_field') + dessert_cals = request.form.get('cals_field') + dessert_origin = request.form.get('origin_field') + dessert_image_url = request.form.get('image_url_field') + + user_id = get_user_id(username) + + # 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, dessert_origin, dessert_image_url, user_id) + return render_template('add.html', dessert=dessert, username=username) + 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, username=username) + + else: + print "Logged-in: No session" + return render_template('login.html', error="Please login.") + + - if request.method == 'GET': - return render_template('add.html') +@app.route('/edit/', methods=['GET', 'POST']) +def edit(id): + if session.get('username'): # this will be executed if 'username' is present in the session + print "Logged-in: Found session" + username = session['username'] - # Because we 'returned' for a 'GET', if we get to this next bit, we must - # have received a POST + # We could define this inside its own function but it's simple enough + # that we don't really need to. - # Get the incoming data from the request.form dictionary. - # The values on the right, inside get(), correspond to the 'name' - # values in the HTML form that was submitted. + dessert = Dessert.query.get(id) - dessert_name = request.form.get('name_field') - dessert_price = request.form.get('price_field') - dessert_cals = request.form.get('cals_field') + user_id = get_user_id(username) - # Now we are checking the input in create_dessert, we need to handle - # the Exception that might happen here. + if request.method == 'GET': + if dessert is None: + return render_template('edit.html',dessert=dessert, error="The dessert ID do not exist.", username=username) + if dessert.user_id != user_id: + dessert = None + return render_template('edit.html',dessert=dessert, error="You can't edit this dessert.", username=username) + else: + return render_template('edit.html',dessert=dessert, error=None, username=username) + + # Because we 'returned' for a 'GET', if we get to this next bit, we must + # have received a POST + + # Get the incoming data from the request.form dictionary. + # The values on the right, inside get(), correspond to the 'name' + # values in the HTML form that was submitted. + + dessert_name = request.form.get('name_field') + dessert_price = request.form.get('price_field') + dessert_cals = request.form.get('cals_field') + dessert_origin = request.form.get('origin_field') + dessert_image_url = request.form.get('image_url_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 = edit_dessert(dessert, dessert_name, dessert_price, dessert_cals, dessert_origin, dessert_image_url) + return render_template('edit.html', dessert=dessert, success=True, username=username) + except Exception as e: + # Oh no, something went wrong! + # We can access the error message via e.message: + return render_template('edit.html', dessert=dessert, error=e.message, username=username) + else: + print "Logged-in: No session" + return render_template('login.html', error="Please login.") - # Wrap the thing we're trying to do in a 'try' block: - try: - dessert = create_dessert(dessert_name, dessert_price, dessert_cals) - return render_template('add.html', 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): + if session.get('username'): # this will be executed if 'username' is present in the session + print "Logged-in: Found session" + username = session['username'] + + dessert = Dessert.query.get(id) - # We could define this inside its own function but it's simple enough - # that we don't really need to. - dessert = Dessert.query.get(id) + user_id = get_user_id(username) - return render_template('details.html', dessert=dessert) + + if dessert is None: + return render_template('details.html',dessert=dessert, error="The dessert ID do not exist.", username=username) + if dessert.user_id != user_id: + dessert = None + return render_template('details.html',dessert=dessert, error="You can't view this dessert.", username=username) + else: + return render_template('details.html',dessert=dessert, error=None, username=username) + + else: + print "Logged-in: No session" + return render_template('login.html', error="Please login.") @app.route('/delete/') def delete(id): + if session.get('username'): # this will be executed if 'username' is present in the session + print "Logged-in: Found session" + username = session['username'] + + user = get_user_by_username(username) + user_id = user.id + + user_id = get_user_id(username) + + desserts = get_desserts(user_id) + + error = delete_dessert(id, user_id) + + + return render_template('index.html', desserts=desserts, error="You can't edit this dessert.", username=username) + else: + print "Logged-in: No session" + return render_template('login.html', error="Please login.", username=username) + + +@app.route('/search', methods=['POST']) +def search(): + if session.get('username'): # this will be executed if 'username' is present in the session + print "Logged-in: Found session" + username = session['username'] + + # Because we 'returned' for a 'GET', if we get to this next bit, we must + # have received a POST + + term = request.form.get('term') + dessert = Dessert.query.filter_by(name=term).first() + print + + if dessert: + return render_template('details.html', dessert=dessert, username=username) + else: + # Oh no, something went wrong! + # We can access the error message via e.message: + return render_template('details.html', dessert=dessert, error="Name does not exist", username=username) + else: + print "Logged-in: No session" + return render_template('login.html', error="Please login.") + +@app.route('/order/') +def order(field): + if session.get('username'): # this will be executed if 'username' is present in the session + print "Logged-in: Found session" + username = session['username'] + + if field == "name" or field == "price" or field == "calories": + desserts = Dessert.query.order_by(field) + return render_template('index.html', desserts=desserts) + else: + desserts = Dessert.query.all() + return render_template('index.html', desserts=desserts) + else: + print "Logged-in: No session" + return render_template('login.html', error="Please login.") + +@app.route('/logout') +def logout_user(): + if session.get('username'): # this will be executed if 'username' is present in the session + username = session['username'] + print "Logout: Deleting settion" + del session['username'] + return redirect("/") - message = delete_dessert(id) +@app.route('/api') +def api(): + desserts = get_all_desserts() + api_dict = {} + for x in desserts: + dessert = dict([('name', x.name), ('price', x.price), ('calories', x.calories), ('origin', x.origin),('image_url', x.image_url), ('user_id', x.user_id)]) + index = x.id + api_dict[index] = dessert + return jsonify(api_dict) - return index() # Look at the URL bar when you do this. What happens? +@app.route('/api/') +def api_id(user_id): + desserts = get_desserts(user_id) + api_dict = {} + for x in desserts: + dessert = dict([('name', x.name), ('price', x.price), ('calories', x.calories), ('origin', x.origin),('image_url', x.image_url), ('user_id', x.user_id)]) + index = x.id + api_dict[index] = dessert + return jsonify(api_dict)