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/app.py b/app.py
index 4347b45..c604601 100644
--- a/app.py
+++ b/app.py
@@ -7,6 +7,7 @@
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///desserts.db'
db = SQLAlchemy(app)
+app.secret_key = '\xa5MY\xb4\xc7\x03\x04\xa5I+C\x86\x1e\xae\x04\xb2-\xe4|\x06\x1e\x7f\x1f4'
if __name__ == "__main__":
@@ -14,7 +15,6 @@
# the app, so we import them. We could do it earlier, but there's
# a risk that we may run into circular dependencies, so we do it at the
# last minute here.
-
from views import *
app.run(debug=True)
diff --git a/backup.csv b/backup.csv
new file mode 100644
index 0000000..e69de29
diff --git a/backup.py b/backup.py
new file mode 100644
index 0000000..94cf89c
--- /dev/null
+++ b/backup.py
@@ -0,0 +1,27 @@
+from models import *
+
+
+def save_data():
+
+ with open('backup.csv', 'w') as f: # Open file 'backup.csv' for writing
+
+ for dessert in Dessert.query.all():
+ # Create a comma separated line
+ line = "{},{},{}\n".format(dessert.name, dessert.price,
+ dessert.calories)
+ # Write it to the file
+ f.write(line)
+
+def load_data():
+
+ with open('backup.csv') as f:
+ for line in f:
+ name, price, calories = line.split(',')
+ d = Dessert(name, price, calories)
+ db.session.add(d)
+ db.session.commit()
+
+
+if __name__=="__main__":
+ # save_data()
+ load_data()
\ No newline at end of file
diff --git a/desserts.db b/desserts.db
new file mode 100644
index 0000000..9dde879
Binary files /dev/null and b/desserts.db differ
diff --git a/models.py b/models.py
index e40d530..d2ea5c4 100644
--- a/models.py
+++ b/models.py
@@ -1,6 +1,17 @@
from app import db
+from flask import session
+
+class Menu(db.Model):
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(100))
+
+ def __init__(self, name):
+ self.name = name
+
+# DESSERTS
class Dessert(db.Model):
# See http://flask-sqlalchemy.pocoo.org/2.0/models/#simple-example
# for details on the column types.
@@ -13,39 +24,176 @@ 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:
return self.calories / self.price
+def create_dessert(new_name, new_price, new_calories, user_id):
+ # Create a dessert with the provided input.
+ # We need every piece of input to be provided.
-class Menu(db.Model):
+ # 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 user_id is None:
+ raise Exception("Need name, price, calories, and user!")
- id = db.Column(db.Integer, primary_key=True)
- name = db.Column(db.String(100))
+ # 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!")
- def __init__(self, name):
- self.name = name
+ if int(new_calories) > 10000:
+ raise Exception("That can't be right. Check the calories again.")
+ if int(new_price) > 100:
+ raise Exception("That can't be right. No dessert is worth that much.")
-def create_dessert(new_name, new_price, new_calories):
- # Create a dessert with the provided input.
- # At first, we will trust the user.
+ if Dessert.query.filter_by(name=new_name).first():
+ raise Exception("There's already one of those. Pick another.")
# 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, user_id)
# Actually add this dessert to the database
db.session.add(dessert)
# Save all pending changes to the database
- db.session.commit()
+ try:
+ db.session.commit()
+ return dessert
+ except:
+ # If something went wrong, explicitly roll back the database
+ db.session.rollback()
+
+def update_dessert(dessert_name, dessert_price, dessert_cals, id):
+ # Create a dessert with the provided input.
+ # We need every piece of input to be provided.
+ dessert = Dessert.query.get(id)
+
+ # Can you think of other ways to write this following check?
+ if dessert_name is None or dessert_price is None or dessert_cals is None:
+ raise Exception("Need name, price and calories!")
+
+ # They can also be empty strings if submitted from a form
+ if dessert_name == '' or dessert_price == '' or dessert_cals == '':
+ raise Exception("Need name, price and calories!")
+
+ if int(dessert_cals) > 10000:
+ raise Exception("That can't be right. Check the calories again.")
+
+ if float(dessert_price) > 100:
+ raise Exception("That can't be right. No dessert is worth that much.")
+
+ # This is where the dessert gets updated
+ dessert.name = dessert_name
+ dessert.price = dessert_price
+ dessert.calories = dessert_cals
+ print 'dessert updated'
- return dessert
+ # 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):
+ 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"
+ else:
+ return "Dessert not found"
+
+
+# USERS
+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 log_in(username, password):
+ if username is None or password is None:
+ raise Exception("Please include your username and password.")
+
+ # They can also be empty strings if submitted from a form
+ if username == '' or password == '':
+ raise Exception("Please include your username and password.")
+
+ try:
+ user = get_user_by_username(username)
+ session["user_id"] = user.id
+ return user
+ except:
+ raise Exception("You don't have an account.")
+
+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, name=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 name:
+ user.name = name
+
+ if avatar:
+ user.avatar = avatar
+
+ db.session.commit()
+ return user
if __name__ == "__main__":
diff --git a/templates/account.html b/templates/account.html
new file mode 100644
index 0000000..c632246
--- /dev/null
+++ b/templates/account.html
@@ -0,0 +1,19 @@
+{% include 'header.html' %}
+
+
+
\ No newline at end of file
diff --git a/templates/add.html b/templates/add.html
index e2d174a..814e7cc 100644
--- a/templates/add.html
+++ b/templates/add.html
@@ -1,27 +1,44 @@
-
Add Dessert
-
-{% if dessert %}
-
-
{{dessert.name}} successfully created! Add another below.
+ An error occurred trying to create your dessert:
+ {{ error }}
+
+ {% endif %}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/create-account.html b/templates/create-account.html
new file mode 100644
index 0000000..7871928
--- /dev/null
+++ b/templates/create-account.html
@@ -0,0 +1,11 @@
+
Confirm Your Account Details
+
+
+
Username: {{ user.username}}
+
Password: {{ user.password }}
+
Real name: {{ user.name }}
+
Email: {{ user.email }}
+
Avatar: {{ user.avatar }}
+
+
+Yup, Let's Log In
\ No newline at end of file
diff --git a/templates/details.html b/templates/details.html
new file mode 100644
index 0000000..c7262a0
--- /dev/null
+++ b/templates/details.html
@@ -0,0 +1,33 @@
+{% include 'header.html' %}
+
+
+
+
+
Dessert Details
+
+
{{ dessert.name }}
+
+
Price: ${{ dessert.price }}
+
+
Calories: {{ dessert.calories }}
+
+
Calories per dollar: {{ dessert.calories_per_dollar() }}
+
\ No newline at end of file
diff --git a/templates/login.html b/templates/login.html
new file mode 100644
index 0000000..18e0c34
--- /dev/null
+++ b/templates/login.html
@@ -0,0 +1,22 @@
+{% include 'header.html' %}
+
+
+
+
\ No newline at end of file
diff --git a/templates/menu.html b/templates/menu.html
new file mode 100644
index 0000000..869f4f8
--- /dev/null
+++ b/templates/menu.html
@@ -0,0 +1,17 @@
+{% include 'header.html' %}
+
+
+
+
\ No newline at end of file
diff --git a/templates/signup.html b/templates/signup.html
new file mode 100644
index 0000000..836fecf
--- /dev/null
+++ b/templates/signup.html
@@ -0,0 +1,28 @@
+{% include 'header.html' %}
+
+
+
+
Sign Up
+
+
Please create an account.
+
+
+
+
\ No newline at end of file
diff --git a/test_models.py b/test_models.py
new file mode 100644
index 0000000..92914c1
--- /dev/null
+++ b/test_models.py
@@ -0,0 +1,114 @@
+import traceback
+
+from models import db, create_dessert, delete_dessert
+
+
+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
+
+
+# ## Testing validation for the create_dessert method.
+
+@check_test
+def test_create_dessert_works():
+ test_name = "Test Dessert"
+ test_price = 10
+ test_calories = 200
+
+ dessert = create_dessert(test_name, test_price, test_calories)
+
+ assert dessert is not None
+
+ # Delete this dessert now we are done
+ db.session.delete(dessert)
+ db.session.commit()
+
+
+@check_test
+def test_create_dessert_wrong_types():
+ # Test we can't create a dessert with the wrong type of input
+
+ test_name = 4
+ test_price = "Cake"
+ test_calories = "None"
+
+ # Initialize the dessert variable so we can check it later.
+ dessert = None
+
+ # "Try" to create the dessert, and do nothing (pass) if we get an error.
+ # After this next block of code, dessert should still be None.
+
+ try:
+ dessert = create_dessert(test_name, test_price, test_calories)
+ except Exception:
+ pass
+
+ # Check dessert is still not created.
+ assert dessert is None
+
+
+@check_test
+def test_create_dessert_missing_data():
+ # Test that if we pass 'None' in, we fail.
+ dessert = None
+
+ try:
+ dessert = create_dessert(None, None, None)
+ except Exception:
+ # You could use e.message in here to check that the error message
+ # is correct if you like.
+ pass
+
+ # Check dessert is still not created.
+ assert dessert is None
+
+ # Also try with empty strings
+ try:
+ dessert = create_dessert('', '', '')
+ except Exception:
+ pass
+
+ # Check dessert is still not created.
+ assert dessert is None
+
+ # Are there other values we should test for? What about a sensible
+ # range for each item?
+
+
+@check_test
+def test_delete_dessert():
+ dessert = create_dessert('test', 0, 0)
+
+ message = delete_dessert(dessert.id)
+
+ assert message == 'Dessert test deleted'
+
+
+@check_test
+def test_delete_nonexistent_dessert():
+ random_id = 394812018 # I'm pretty sure a dessert by this ID doesn't exist
+
+ message = delete_dessert(random_id)
+
+ assert message == 'Dessert not found'
+
+
+if __name__ == "__main__":
+
+ # Run every method in this file which starts with test_.
+
+ 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 and run it!
+ if item.startswith('test_'):
+ globals()[item]()
diff --git a/views.py b/views.py
index 4e42ae5..247b851 100644
--- a/views.py
+++ b/views.py
@@ -1,26 +1,29 @@
-from flask import render_template, request
+from flask import render_template, request, session
-from models import Dessert, create_dessert
+from models import *
from app import app
@app.route('/')
def index():
+ return render_template('index.html')
+ # desserts = Dessert.query.all()
+ # return render_template('index.html', desserts=desserts)
- desserts = Dessert.query.all()
-
- return render_template('index.html', desserts=desserts)
-
+@app.route('/menu')
+def menu():
+ user_id = session.get("user_id")
+ if user_id:
+ user = User.query.get(user_id)
+ desserts = Dessert.query.filter_by(user_id=user.id).all()
+ return render_template('menu.html', desserts=desserts, user=user)
@app.route('/add', methods=['GET', 'POST'])
def add():
-
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.
@@ -29,5 +32,90 @@ def add():
dessert_price = request.form.get('price_field')
dessert_cals = request.form.get('cals_field')
- dessert = create_dessert(dessert_name, dessert_price, dessert_cals)
- return render_template('add.html', dessert=dessert)
+ user_id = session.get("user_id")
+ user = User.query.get(user_id)
+ dessert_user = user.id
+
+ # 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_user)
+ 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('/edit/', methods=['GET', 'POST'])
+def edit(id):
+ dessert = Dessert.query.get(id)
+
+ if request.method == 'GET':
+ return render_template('edit.html', dessert=dessert)
+
+ dessert_name = request.form.get('name_field')
+ dessert_price = request.form.get('price_field')
+ dessert_cals = request.form.get('cals_field')
+
+ # Wrap the thing we're trying to do in a 'try' block:
+ try:
+ dessert = update_dessert(dessert_name, dessert_price, dessert_cals, id)
+ return render_template('details.html', dessert=dessert, id=dessert.id)
+ except Exception as e:
+ # Oh no, something went wrong!
+ # We can access the error message via e.message:
+ return render_template('edit.html', error=e.message, dessert=dessert)
+
+@app.route('/desserts/')
+def view_dessert(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)
+ return render_template('details.html', dessert=dessert)
+
+@app.route('/delete/')
+def delete(id):
+ message = delete_dessert(id)
+ return menu() # Look at the URL bar when you do this. What happens?
+
+
+# USER MANAGEMENT
+@app.route("/signup")
+def signup():
+ return render_template("signup.html")
+
+@app.route("/create-account", methods=['POST'])
+def create_account():
+ username = request.form.get('username')
+ password = request.form.get('password')
+ name = request.form.get('name')
+ email = request.form.get('email')
+ avatar = request.form.get('avatar')
+ user = create_user(username, email, password, name, avatar)
+ return render_template("account.html", user=user)
+
+@app.route("/login")
+def login():
+ return render_template("login.html")
+
+@app.route("/user", methods=['POST'])
+def user_account():
+ username = request.form.get('name')
+ password = request.form.get('password')
+
+ try:
+ user = log_in(username, password)
+ return render_template("account.html", user=user)
+ except Exception as e:
+ # Oh no, something went wrong!
+ # We can access the error message via e.message:
+ return render_template('index.html', error=e.message)
+
+@app.route("/user/")
+def view_user(id):
+ user = User.query.get(id)
+ return render_template("account.html", user=user)
+
+