From 653a7160f526304182474c49bfccf87af4163895 Mon Sep 17 00:00:00 2001 From: Alan Grissett Date: Tue, 17 Feb 2015 19:55:31 -0500 Subject: [PATCH 1/6] Functional to-do list done. --- schema.sql | 19 ++++++ static/style.css | 35 ++++++++++ templates/.DS_Store | Bin 0 -> 6148 bytes templates/create_user.html | 16 +++++ templates/display_done.html | 14 ++++ templates/layout.html | 17 +++++ templates/login.html | 15 +++++ templates/show_entries.html | 22 +++++++ to_do.py | 125 ++++++++++++++++++++++++++++++++++++ 9 files changed, 263 insertions(+) create mode 100644 schema.sql create mode 100644 static/style.css create mode 100644 templates/.DS_Store create mode 100644 templates/create_user.html create mode 100644 templates/display_done.html create mode 100644 templates/layout.html create mode 100644 templates/login.html create mode 100644 templates/show_entries.html create mode 100644 to_do.py diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..cc2b4e9 --- /dev/null +++ b/schema.sql @@ -0,0 +1,19 @@ +drop table if exists users; +drop table if exists list; + +create table users ( + username text primary key not null, + password text not null +); + +create table list ( + id integer primary key autoincrement, + owner text not null, + task text not null +); + +create table done ( + id integer primary key autoincrement, + owner text not null, + task text not null +); diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..1ddf8c6 --- /dev/null +++ b/static/style.css @@ -0,0 +1,35 @@ +body { font-family: sans-serif; background: #eee; } +a { color: #377ba8; } +h1, h2, li { color: #000; } +h1, h2, li { font-family: 'Georgia', serif; margin: 0; } +h2, li { font-size: 1.2em; } + +.page { margin: 2em auto; width: 35em; border: 5px solid #ccc; + padding: 0.8em; background: white; } +.entries { list-style: none; margin: 0; padding: 0; } +.entries li { margin: 0.8em 1.2em; } +.entries li h2 { margin-left: 1em; } +.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; } +.add-entry dl { font-weight: bold; } +.metanav { text-align: right; font-size: 0.8em; padding: 0.3em; + margin-bottom: 1em; background: #fafafa; } +.flash { background: #cee5F5; padding: 0.5em; + border: 1px solid #aacbe2; } +.error { background: #f0d6d6; padding: 0.5em; } + +div.ui-btn, input[type="submit"] { + width:60px; + background:#09C; + color:#fff; + font-family: Tahoma, Geneva, sans-serif; + height:20px; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; + border: 1p solid #999; + margin-top: 2px +} +div.ui-btn, input[type="submit"]:hover{ + background:#cccccc; + color:#09C; +} diff --git a/templates/.DS_Store b/templates/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..98c3ad929bed0ebc14cb3017b50e066f295f6095 GIT binary patch literal 6148 zcmeHKO-sW-5PcIXRC`Hp9&_|0_zyy*u!(Wcl!Y@|&QZ~1}#0RN4?*&T{a%~hz( z!0g*>_RVJBhD-*4j5ft3kOJt_6_XP-znI=v-?PL6X7KbRX;F-UoWQ=#d0R<{78L=Zma^H$D zFlNPfr|x3my2UyrtHLwaUUqUmTAU%Db57P9pK?ZC&7CjUTX#i}+g1c#y14?bfGcp| z3NUA@^#>8Xbp>1jS757vejhTrVs6+(44)1*p#&iII2??9ttFI`8RmvfMBbrEq!J@l zti+H==XhrGa>FKKq(iLu5L;QSLXogK<7Z(yB#-E=E8q(3DsU7pQ_cTZ-Sz)2$*)`i zSKwbMAieS3c*HGPZr$6Q=Gu(@NLSN&O~fsQosfz-S5xsBJs8I`?GSUrCL%{@{v!}% L@WvJRQw6>OqyAe= literal 0 HcmV?d00001 diff --git a/templates/create_user.html b/templates/create_user.html new file mode 100644 index 0000000..d1afd09 --- /dev/null +++ b/templates/create_user.html @@ -0,0 +1,16 @@ +{% extends "layout.html" %} +{% block body %} +

Choose a username and password:

+ {% if error %}

Error:{{ error }}{% endif %} +

+
+
Username: +
+
Password: +
+
+
+
+ Return to login screen. + +{% endblock %} diff --git a/templates/display_done.html b/templates/display_done.html new file mode 100644 index 0000000..a7ef7b0 --- /dev/null +++ b/templates/display_done.html @@ -0,0 +1,14 @@ +{% extends "layout.html" %} +{% block body %} +
+
Finished Items: +
+
    + {% for entry in entries %} +
  • {{ entry[0]|safe }} + {% else %} +
  • Nothing done yet! Get to work! +
+ {% endfor %} +

See things to do. +{% endblock %} diff --git a/templates/layout.html b/templates/layout.html new file mode 100644 index 0000000..8cf59dc --- /dev/null +++ b/templates/layout.html @@ -0,0 +1,17 @@ + + + To Do List + + +

+

To Do List:

+
+ {% if session.logged_in %} + log out + {% endif %} +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block body %}{% endblock %} +
diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..e0bd539 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,15 @@ +{% extends "layout.html" %} +{% block body %} +

Login

+ {% if error %}

Error:{{ error }}{% endif %} +

+
+
Username: +
+
Password: +
+
+
+
+ Create Account! +{% endblock %} diff --git a/templates/show_entries.html b/templates/show_entries.html new file mode 100644 index 0000000..5e8679b --- /dev/null +++ b/templates/show_entries.html @@ -0,0 +1,22 @@ +{% extends "layout.html" %} +{% block body %} +
+
+
Add Task: +
+
+
+
+
    +
    + {% for entry in entries %} +
  • {{ entry[1]|safe }} + {% else %} +
  • Nothing to do! + {% endfor %} + {% if entries %} +
  • + {% endif %} +
+

See completed items.

+{% endblock %} diff --git a/to_do.py b/to_do.py new file mode 100644 index 0000000..c306b32 --- /dev/null +++ b/to_do.py @@ -0,0 +1,125 @@ +import sqlite3 +from flask import Flask, request, session, g, redirect, url_for, abort,\ + render_template, flash +from hashlib import md5 + + +DATABASE = '/tmp/to_do.db' +SECRET_KEY = 'development key' + +app = Flask(__name__) +app.config.from_object(__name__) + + +def connect_db(): + return sqlite3.connect(app.config['DATABASE']) + + +@app.before_request +def before_request(): + g.db = connect_db() + + +@app.teardown_request +def teardown_request(exception): + db = getattr(g, 'db', None) + if db is not None: + db.close() + + +@app.route('/') +def show_entries(): + if not session.get('logged_in'): + return redirect(url_for('login')) + else: + cur = g.db.execute('select id, task from list where owner = ?', + [session.get('username')]) + entries = [item for item in cur.fetchall()] + return render_template('show_entries.html', entries=entries) + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + error = None + if request.method == 'POST': + if not g.db.execute('select username from users where username = ?', + [request.form['username']]).fetchall(): + error = 'Invalid username' + elif (md5(request.form['password'].encode('utf-8')).digest() != + g.db.execute( + 'select password from users where username = ?', + [request.form['username']]).fetchall()[0][0]): + error = 'Invalid password' + else: + session['logged_in'] = True + session['username'] = request.form['username'] + flash('Login successful!') + return redirect(url_for('show_entries')) + return render_template('login.html', error=error) + + +@app.route('/logout') +def logout(): + session.pop('logged_in', None) + session.pop('username', None) + flash('Have a nice day!') + return redirect(url_for('show_entries')) + + +@app.route('/create_user', methods=["GET", "POST"]) +def create_user(): + error = None + if request.method == 'POST': + if not request.form['password'] or not request.form['username']: + error = 'Username an password cannot be blank.' + elif g.db.execute('select username from users where username = ?', + [request.form['username']]).fetchall(): + error = 'Username already in use.' + else: + g.db.execute( + 'insert into users (username, password) values (?, ?)', + [request.form['username'], + md5(request.form['password'].encode('utf-8')).digest()]) + g.db.commit() + flash('Account successfully created!') + return redirect(url_for('login')) + return render_template('create_user.html', error=error) + + +@app.route('/add', methods=['POST']) +def add_entry(): + if not session.get('logged_in'): + abort(401) + g.db.execute('insert into list (owner, task) values (?, ?)', + [session.get('username'), request.form['todo']]) + g.db.commit() + flash('New item added to list!') + return redirect(url_for('show_entries')) + + +@app.route('/remove', methods=["POST"]) +def remove_entry(): + for item in request.form.getlist('item'): + owner, task = g.db.execute('select owner, task from list where id = ?', + [item]).fetchall()[0] + g.db.execute('delete from list where id = ?', [item]) + g.db.execute('insert into done (owner, task) values (?, ?)', + [owner, task]) + g.db.commit() + flash('Item moved to complete list!') + return redirect(url_for('show_entries')) + + +@app.route('/done', methods=["GET"]) +def display_done(): + if not session.get('logged_in'): + return redirect(url_for('login')) + else: + cur = g.db.execute('select task from done where owner = ?', + [session.get('username')]) + entries = [item for item in cur.fetchall()] + return render_template('display_done.html', entries=entries) + + +if __name__ == '__main__': + app.run() From dd010399be309de8204a32fee8fd9fcb8e8a2e23 Mon Sep 17 00:00:00 2001 From: Alan Grissett Date: Tue, 17 Feb 2015 21:22:52 -0500 Subject: [PATCH 2/6] Added testing module --- to_do.py | 10 +++++++- todo_test.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 todo_test.py diff --git a/to_do.py b/to_do.py index c306b32..6ebe271 100644 --- a/to_do.py +++ b/to_do.py @@ -2,6 +2,7 @@ from flask import Flask, request, session, g, redirect, url_for, abort,\ render_template, flash from hashlib import md5 +from contextlib import closing DATABASE = '/tmp/to_do.db' @@ -15,6 +16,13 @@ def connect_db(): return sqlite3.connect(app.config['DATABASE']) +def init_db(): + with closing(connect_db()) as db: + with app.open_resource('schema.sql', mode='r') as f: + db.cursor().executescript(f.read()) + db.commit() + + @app.before_request def before_request(): g.db = connect_db() @@ -106,7 +114,7 @@ def remove_entry(): g.db.execute('insert into done (owner, task) values (?, ?)', [owner, task]) g.db.commit() - flash('Item moved to complete list!') + flash('Item moved to completed list!') return redirect(url_for('show_entries')) diff --git a/todo_test.py b/todo_test.py new file mode 100644 index 0000000..cdaf68d --- /dev/null +++ b/todo_test.py @@ -0,0 +1,68 @@ +import os +import to_do +import unittest +import tempfile +from hashlib import md5 + +class ToDoTestCase(unittest.TestCase): + + def setUp(self): + self.db_fd, to_do.app.config['DATABASE'] = tempfile.mkstemp() + to_do.app.config['TESTING'] = True + self.app = to_do.app.test_client() + to_do.init_db() + self.app.post('/create_user', data=dict( + username="Ted", + password="Ted"), + follow_redirects=True) + + def tearDown(self): + os.close(self.db_fd) + os.unlink(to_do.app.config['DATABASE']) + + def login(self, username, password): + return self.app.post('/login', data=dict( + username=username, + password=password), + follow_redirects=True) + + def logout(self): + return self.app.get('/logout', follow_redirects=True) + + def test_empty_db(self): + rv = self.app.get('/', follow_redirects=True) + assert 'Login' in str(rv.data) + + def test_create_user(self): + rv = self.app.post('/create_user', data=dict( + username="Ted2", + password="Ted"), + follow_redirects=True) + assert 'Account successfully created!' in str(rv.data) + + def test_login_logout(self): + rv = self.login('Ted', 'Ted') + assert 'Login successful!' in str(rv.data) + rv = self.logout() + assert 'Have a nice day!' in str(rv.data) + rv = self.login('Tedx', 'Ted') + assert 'Invalid username' in str(rv.data) + rv = self.login('Ted', 'defaultx') + assert 'Invalid password' in str(rv.data) + + def test_add_remove_messages(self): + self.login('Ted', 'Ted') + rv = self.app.post('/add', data=dict( + todo='Test' + ), follow_redirects=True) + assert 'Nothing to do!' not in str(rv.data) + assert 'Test' in str(rv.data) + rv = self.app.post('/remove', data=dict( + item=1), follow_redirects=True) + assert 'Item moved to completed list!' in str(rv.data) + rv = self.app.get('/done') + assert 'Test' in str(rv.data) + + +if __name__ == '__main__': + unittest.main() From a90ac69302c5479384f6dc25f0c1aaf259c67dfe Mon Sep 17 00:00:00 2001 From: Alan Grissett Date: Tue, 17 Feb 2015 22:15:39 -0500 Subject: [PATCH 3/6] Fixed pep8 errors. --- to_do.py | 6 +++--- todo_test.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/to_do.py b/to_do.py index 6ebe271..9cbbe3d 100644 --- a/to_do.py +++ b/to_do.py @@ -1,6 +1,6 @@ import sqlite3 from flask import Flask, request, session, g, redirect, url_for, abort,\ - render_template, flash + render_template, flash from hashlib import md5 from contextlib import closing @@ -57,7 +57,7 @@ def login(): g.db.execute( 'select password from users where username = ?', [request.form['username']]).fetchall()[0][0]): - error = 'Invalid password' + error = 'Invalid password' else: session['logged_in'] = True session['username'] = request.form['username'] @@ -87,7 +87,7 @@ def create_user(): g.db.execute( 'insert into users (username, password) values (?, ?)', [request.form['username'], - md5(request.form['password'].encode('utf-8')).digest()]) + md5(request.form['password'].encode('utf-8')).digest()]) g.db.commit() flash('Account successfully created!') return redirect(url_for('login')) diff --git a/todo_test.py b/todo_test.py index cdaf68d..a1387c6 100644 --- a/todo_test.py +++ b/todo_test.py @@ -4,6 +4,7 @@ import tempfile from hashlib import md5 + class ToDoTestCase(unittest.TestCase): def setUp(self): @@ -63,6 +64,6 @@ def test_add_remove_messages(self): rv = self.app.get('/done') assert 'Test' in str(rv.data) - + if __name__ == '__main__': unittest.main() From 7852cab8b9cdfa18a0f0b8b177b6ed245f29add6 Mon Sep 17 00:00:00 2001 From: Alan Grissett Date: Wed, 18 Feb 2015 07:51:43 -0500 Subject: [PATCH 4/6] Added missing tag in show_entries.html --- templates/show_entries.html | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/show_entries.html b/templates/show_entries.html index 5e8679b..decb49a 100644 --- a/templates/show_entries.html +++ b/templates/show_entries.html @@ -17,6 +17,7 @@ {% if entries %}
  • {% endif %} +

    See completed items.

    {% endblock %} From 351a1fa03cd58745bf6823c2a136a2ccb76fc5ec Mon Sep 17 00:00:00 2001 From: Alan Grissett Date: Thu, 19 Feb 2015 10:59:44 -0500 Subject: [PATCH 5/6] Sqlalchemy --- run.py | 13 ++ schema.sql | 19 --- templates/show_entries.html | 22 --- to_do.py | 133 ------------------ todo/__init__.py | 13 ++ todo/forms.py | 6 + todo/models.py | 43 ++++++ {static => todo/static}/.gitkeep | 0 {static => todo/static}/style.css | 20 ++- {templates => todo/templates}/.DS_Store | Bin {templates => todo/templates}/.gitkeep | 0 todo/templates/add_entry.html | 12 ++ .../templates}/create_user.html | 0 .../templates}/display_done.html | 4 +- todo/templates/index.html | 9 ++ {templates => todo/templates}/layout.html | 11 +- {templates => todo/templates}/login.html | 0 todo/templates/show_entries.html | 15 ++ todo_test.py => todo/test/todo_test.py | 0 todo/to_do.py | 133 ++++++++++++++++++ todo/views.py | 115 +++++++++++++++ 21 files changed, 385 insertions(+), 183 deletions(-) create mode 100644 run.py delete mode 100644 schema.sql delete mode 100644 templates/show_entries.html delete mode 100644 to_do.py create mode 100644 todo/__init__.py create mode 100644 todo/forms.py create mode 100644 todo/models.py rename {static => todo/static}/.gitkeep (100%) rename {static => todo/static}/style.css (72%) rename {templates => todo/templates}/.DS_Store (100%) rename {templates => todo/templates}/.gitkeep (100%) create mode 100644 todo/templates/add_entry.html rename {templates => todo/templates}/create_user.html (100%) rename {templates => todo/templates}/display_done.html (70%) create mode 100644 todo/templates/index.html rename {templates => todo/templates}/layout.html (56%) rename {templates => todo/templates}/login.html (100%) create mode 100644 todo/templates/show_entries.html rename todo_test.py => todo/test/todo_test.py (100%) create mode 100644 todo/to_do.py create mode 100644 todo/views.py diff --git a/run.py b/run.py new file mode 100644 index 0000000..7d99f91 --- /dev/null +++ b/run.py @@ -0,0 +1,13 @@ +from flask.ext.script import Manager +from flask.ext.migrate import Migrate, MigrateCommand +from flask.ext.script.commands import ShowUrls, Clean + +from todo import app, db + +migrate = Migrate(app, db) +manager = Manager(app) +manager.add_command('db', MigrateCommand) +manager.add_command('show-urls', ShowUrls()) +manager.add_command('clean', Clean()) + +manager.run() diff --git a/schema.sql b/schema.sql deleted file mode 100644 index cc2b4e9..0000000 --- a/schema.sql +++ /dev/null @@ -1,19 +0,0 @@ -drop table if exists users; -drop table if exists list; - -create table users ( - username text primary key not null, - password text not null -); - -create table list ( - id integer primary key autoincrement, - owner text not null, - task text not null -); - -create table done ( - id integer primary key autoincrement, - owner text not null, - task text not null -); diff --git a/templates/show_entries.html b/templates/show_entries.html deleted file mode 100644 index 5e8679b..0000000 --- a/templates/show_entries.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "layout.html" %} -{% block body %} -
    -
    -
    Add Task: -
    -
    -
    -
    -
      -
      - {% for entry in entries %} -
    • {{ entry[1]|safe }} - {% else %} -
    • Nothing to do! - {% endfor %} - {% if entries %} -
    • - {% endif %} -
    -

    See completed items.

    -{% endblock %} diff --git a/to_do.py b/to_do.py deleted file mode 100644 index 9cbbe3d..0000000 --- a/to_do.py +++ /dev/null @@ -1,133 +0,0 @@ -import sqlite3 -from flask import Flask, request, session, g, redirect, url_for, abort,\ - render_template, flash -from hashlib import md5 -from contextlib import closing - - -DATABASE = '/tmp/to_do.db' -SECRET_KEY = 'development key' - -app = Flask(__name__) -app.config.from_object(__name__) - - -def connect_db(): - return sqlite3.connect(app.config['DATABASE']) - - -def init_db(): - with closing(connect_db()) as db: - with app.open_resource('schema.sql', mode='r') as f: - db.cursor().executescript(f.read()) - db.commit() - - -@app.before_request -def before_request(): - g.db = connect_db() - - -@app.teardown_request -def teardown_request(exception): - db = getattr(g, 'db', None) - if db is not None: - db.close() - - -@app.route('/') -def show_entries(): - if not session.get('logged_in'): - return redirect(url_for('login')) - else: - cur = g.db.execute('select id, task from list where owner = ?', - [session.get('username')]) - entries = [item for item in cur.fetchall()] - return render_template('show_entries.html', entries=entries) - - -@app.route('/login', methods=['GET', 'POST']) -def login(): - error = None - if request.method == 'POST': - if not g.db.execute('select username from users where username = ?', - [request.form['username']]).fetchall(): - error = 'Invalid username' - elif (md5(request.form['password'].encode('utf-8')).digest() != - g.db.execute( - 'select password from users where username = ?', - [request.form['username']]).fetchall()[0][0]): - error = 'Invalid password' - else: - session['logged_in'] = True - session['username'] = request.form['username'] - flash('Login successful!') - return redirect(url_for('show_entries')) - return render_template('login.html', error=error) - - -@app.route('/logout') -def logout(): - session.pop('logged_in', None) - session.pop('username', None) - flash('Have a nice day!') - return redirect(url_for('show_entries')) - - -@app.route('/create_user', methods=["GET", "POST"]) -def create_user(): - error = None - if request.method == 'POST': - if not request.form['password'] or not request.form['username']: - error = 'Username an password cannot be blank.' - elif g.db.execute('select username from users where username = ?', - [request.form['username']]).fetchall(): - error = 'Username already in use.' - else: - g.db.execute( - 'insert into users (username, password) values (?, ?)', - [request.form['username'], - md5(request.form['password'].encode('utf-8')).digest()]) - g.db.commit() - flash('Account successfully created!') - return redirect(url_for('login')) - return render_template('create_user.html', error=error) - - -@app.route('/add', methods=['POST']) -def add_entry(): - if not session.get('logged_in'): - abort(401) - g.db.execute('insert into list (owner, task) values (?, ?)', - [session.get('username'), request.form['todo']]) - g.db.commit() - flash('New item added to list!') - return redirect(url_for('show_entries')) - - -@app.route('/remove', methods=["POST"]) -def remove_entry(): - for item in request.form.getlist('item'): - owner, task = g.db.execute('select owner, task from list where id = ?', - [item]).fetchall()[0] - g.db.execute('delete from list where id = ?', [item]) - g.db.execute('insert into done (owner, task) values (?, ?)', - [owner, task]) - g.db.commit() - flash('Item moved to completed list!') - return redirect(url_for('show_entries')) - - -@app.route('/done', methods=["GET"]) -def display_done(): - if not session.get('logged_in'): - return redirect(url_for('login')) - else: - cur = g.db.execute('select task from done where owner = ?', - [session.get('username')]) - entries = [item for item in cur.fetchall()] - return render_template('display_done.html', entries=entries) - - -if __name__ == '__main__': - app.run() diff --git a/todo/__init__.py b/todo/__init__.py new file mode 100644 index 0000000..fd7cd0a --- /dev/null +++ b/todo/__init__.py @@ -0,0 +1,13 @@ +from flask import Flask +from flask.ext.sqlalchemy import SQLAlchemy + +DATABASE = '/tmp/to_dov2.db' +DEBUG = True +SECRET_KEY = 'development key' +SQLALCHEMY_DATABASE_URI = 'sqlite:///' + DATABASE + +app = Flask(__name__) +app.config.from_object(__name__) +db = SQLAlchemy(app) + +from . import views diff --git a/todo/forms.py b/todo/forms.py new file mode 100644 index 0000000..9df6760 --- /dev/null +++ b/todo/forms.py @@ -0,0 +1,6 @@ +from flask_wtf import Form +from wtforms import StringField +from wtforms.validators import DataRequired + +class TodoForm(Form): + text = StringField('text', validators=[DataRequired()]) diff --git a/todo/models.py b/todo/models.py new file mode 100644 index 0000000..40bf25f --- /dev/null +++ b/todo/models.py @@ -0,0 +1,43 @@ +from . import db + +class Users(db.Model): + username = db.Column(db.String(32), primary_key=True) + password = db.Column(db.String(120)) + + def __init__(self, username, password): + self.username = username + self.password = password + + def __repr__(self): + return "".format(self.username) + + +class ToDo(db.Model): + + id = db.Column(db.Integer, primary_key=True) + owner = db.Column(db.String(32), db.ForeignKey(Users.username)) + text = db.Column(db.String(255), nullable=False) + due_date = db.Column(db.DateTime) + completed_at = db.Column(db.DateTime) + + def __init__(self, owner, text): + self.owner = owner + self.text = text + + def __repr__(self): + return "".format(self.text) + +class Notes(db.Model): + + id = db.Column(db.Integer, primary_key=True) + owner = db.Column(db.String(32), nullable=False) + todo_reference = db.Column(db.Integer, db.ForeignKey(ToDo.id)) + text = db.Column(db.String(255), nullable=False) + + def __init__(owner, todo_reference, text): + self.owner = owner + self.todo_reference = todo_reference + self.text = text + + def __repr__(self): + return "".format(self.text) diff --git a/static/.gitkeep b/todo/static/.gitkeep similarity index 100% rename from static/.gitkeep rename to todo/static/.gitkeep diff --git a/static/style.css b/todo/static/style.css similarity index 72% rename from static/style.css rename to todo/static/style.css index 1ddf8c6..ebb78a7 100644 --- a/static/style.css +++ b/todo/static/style.css @@ -17,19 +17,31 @@ h2, li { font-size: 1.2em; } border: 1px solid #aacbe2; } .error { background: #f0d6d6; padding: 0.5em; } -div.ui-btn, input[type="submit"] { - width:60px; +div.ui-btn, button, input[type="submit"] { + /*width:60px;*/ background:#09C; color:#fff; font-family: Tahoma, Geneva, sans-serif; - height:20px; + /*height:20px;*/ -webkit-border-radius: 15px; -moz-border-radius: 15px; border-radius: 15px; border: 1p solid #999; margin-top: 2px } -div.ui-btn, input[type="submit"]:hover{ +div.ui-btn, button:hover, input[type="submit"]:hover{ background:#cccccc; color:#09C; } +div.ui-btn, input[class="edit"] { + /*width:60px;*/ + background:#cccccc; + color:#09C; + font-family: Tahoma, Geneva, sans-serif; + /*height:20px;*/ + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; + border: 1p solid #999; + margin-top: 2px +} diff --git a/templates/.DS_Store b/todo/templates/.DS_Store similarity index 100% rename from templates/.DS_Store rename to todo/templates/.DS_Store diff --git a/templates/.gitkeep b/todo/templates/.gitkeep similarity index 100% rename from templates/.gitkeep rename to todo/templates/.gitkeep diff --git a/todo/templates/add_entry.html b/todo/templates/add_entry.html new file mode 100644 index 0000000..c1b4ab0 --- /dev/null +++ b/todo/templates/add_entry.html @@ -0,0 +1,12 @@ +{% extends "layout.html" %} +{% block body %} + +
    +
    Add Task: +
    +
    Deadline: +
    +
    +
    + +{% endblock %} diff --git a/templates/create_user.html b/todo/templates/create_user.html similarity index 100% rename from templates/create_user.html rename to todo/templates/create_user.html diff --git a/templates/display_done.html b/todo/templates/display_done.html similarity index 70% rename from templates/display_done.html rename to todo/templates/display_done.html index a7ef7b0..ceaaa3e 100644 --- a/templates/display_done.html +++ b/todo/templates/display_done.html @@ -5,10 +5,10 @@
      {% for entry in entries %} -
    • {{ entry[0]|safe }} +
    • {{ entry.text|safe }}
    • +
    • Completed on: {{ entry.completed_at }}
    • {% else %}
    • Nothing done yet! Get to work!
    {% endfor %} -

    See things to do. {% endblock %} diff --git a/todo/templates/index.html b/todo/templates/index.html new file mode 100644 index 0000000..fe2e11c --- /dev/null +++ b/todo/templates/index.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} +{% block body %} +

    Please Select an Action:

    +
      +
    • +
    • +
    • +
    +{% endblock %} diff --git a/templates/layout.html b/todo/templates/layout.html similarity index 56% rename from templates/layout.html rename to todo/templates/layout.html index 8cf59dc..661055d 100644 --- a/templates/layout.html +++ b/todo/templates/layout.html @@ -1,17 +1,22 @@ To Do List - +

    To Do List:

    +
    + {% if request.path != "/" %} + + {% endif %} {% if session.logged_in %} - log out + {% endif %}
    {% for message in get_flashed_messages() %}
    {{ message }}
    {% endfor %} - {% block body %}{% endblock %} + {% block body %} + {% endblock %}
    diff --git a/templates/login.html b/todo/templates/login.html similarity index 100% rename from templates/login.html rename to todo/templates/login.html diff --git a/todo/templates/show_entries.html b/todo/templates/show_entries.html new file mode 100644 index 0000000..233a82e --- /dev/null +++ b/todo/templates/show_entries.html @@ -0,0 +1,15 @@ +{% extends "layout.html" %} +{% block body %} +
      +
      + {% for entry in entries %} +
    • {{ entry.text|safe }} Deadline: {{ entry.due_date }}
    • + {% else %} +
    • Nothing to do!
    • + {% endfor %} + {% if entries %} +
    • + {% endif %} +
      +
    +{% endblock %} diff --git a/todo_test.py b/todo/test/todo_test.py similarity index 100% rename from todo_test.py rename to todo/test/todo_test.py diff --git a/todo/to_do.py b/todo/to_do.py new file mode 100644 index 0000000..f072256 --- /dev/null +++ b/todo/to_do.py @@ -0,0 +1,133 @@ +# import sqlite3 +# from flask import Flask, request, session, g, redirect, url_for, abort,\ +# render_template, flash +# from hashlib import md5 +# from contextlib import closing +# +# +# DATABASE = '/tmp/to_do.db' +# SECRET_KEY = 'development key' +# +# app = Flask(__name__) +# app.config.from_object(__name__) +# +# +# def connect_db(): +# return sqlite3.connect(app.config['DATABASE']) +# +# +# def init_db(): +# with closing(connect_db()) as db: +# with app.open_resource('schema.sql', mode='r') as f: +# db.cursor().executescript(f.read()) +# db.commit() +# +# +# @app.before_request +# def before_request(): +# g.db = connect_db() +# +# +# @app.teardown_request +# def teardown_request(exception): +# db = getattr(g, 'db', None) +# if db is not None: +# db.close() +# +# +# @app.route('/') +# def show_entries(): +# if not session.get('logged_in'): +# return redirect(url_for('login')) +# else: +# cur = g.db.execute('select id, task from list where owner = ?', +# [session.get('username')]) +# entries = [item for item in cur.fetchall()] +# return render_template('show_entries.html', entries=entries) +# +# +# @app.route('/login', methods=['GET', 'POST']) +# def login(): +# error = None +# if request.method == 'POST': +# if not g.db.execute('select username from users where username = ?', +# [request.form['username']]).fetchall(): +# error = 'Invalid username' +# elif (md5(request.form['password'].encode('utf-8')).digest() != +# g.db.execute( +# 'select password from users where username = ?', +# [request.form['username']]).fetchall()[0][0]): +# error = 'Invalid password' +# else: +# session['logged_in'] = True +# session['username'] = request.form['username'] +# flash('Login successful!') +# return redirect(url_for('show_entries')) +# return render_template('login.html', error=error) +# +# +# @app.route('/logout') +# def logout(): +# session.pop('logged_in', None) +# session.pop('username', None) +# flash('Have a nice day!') +# return redirect(url_for('show_entries')) +# +# +# @app.route('/create_user', methods=["GET", "POST"]) +# def create_user(): +# error = None +# if request.method == 'POST': +# if not request.form['password'] or not request.form['username']: +# error = 'Username an password cannot be blank.' +# elif g.db.execute('select username from users where username = ?', +# [request.form['username']]).fetchall(): +# error = 'Username already in use.' +# else: +# g.db.execute( +# 'insert into users (username, password) values (?, ?)', +# [request.form['username'], +# md5(request.form['password'].encode('utf-8')).digest()]) +# g.db.commit() +# flash('Account successfully created!') +# return redirect(url_for('login')) +# return render_template('create_user.html', error=error) +# +# +# @app.route('/add', methods=['POST']) +# def add_entry(): +# if not session.get('logged_in'): +# abort(401) +# g.db.execute('insert into list (owner, task) values (?, ?)', +# [session.get('username'), request.form['todo']]) +# g.db.commit() +# flash('New item added to list!') +# return redirect(url_for('show_entries')) +# +# +# @app.route('/remove', methods=["POST"]) +# def remove_entry(): +# for item in request.form.getlist('item'): +# owner, task = g.db.execute('select owner, task from list where id = ?', +# [item]).fetchall()[0] +# g.db.execute('delete from list where id = ?', [item]) +# g.db.execute('insert into done (owner, task) values (?, ?)', +# [owner, task]) +# g.db.commit() +# flash('Item moved to completed list!') +# return redirect(url_for('show_entries')) +# +# +# @app.route('/done', methods=["GET"]) +# def display_done(): +# if not session.get('logged_in'): +# return redirect(url_for('login')) +# else: +# cur = g.db.execute('select task from done where owner = ?', +# [session.get('username')]) +# entries = [item for item in cur.fetchall()] +# return render_template('display_done.html', entries=entries) +# +# +# if __name__ == '__main__': +# app.run() diff --git a/todo/views.py b/todo/views.py new file mode 100644 index 0000000..5061217 --- /dev/null +++ b/todo/views.py @@ -0,0 +1,115 @@ +from datetime import datetime + +from flask import render_template, request, redirect, url_for, flash, session +from hashlib import md5 +from .models import Users, ToDo, Notes +from . import app, db + + +@app.route('/') +def main_menu(): + if not session.get('logged_in'): + return redirect(url_for('login')) + else: + return render_template('index.html') + + +@app.route('/show_entries') +def show_entries(): + if not session.get('logged_in'): + return redirect(url_for('login')) + else: + current = ToDo.query.filter( + ToDo.owner==session.get('username')).filter( + ToDo.completed_at==None).all() + for item in current: + if item.due_date: + item.due_date = item.due_date.strftime("%m/%d/%Y") + return render_template('show_entries.html', entries=current) + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + error = None + + if request.method == 'POST': + username = request.form['username'] + password = md5(request.form['password'].encode('utf-8')).digest() + if not Users.query.get(username): + error = 'Invalid username' + elif not Users.query.get(username).password == password: + error = 'Invalid password' + else: + session['logged_in'] = True + session['username'] = request.form['username'] + flash('Login successful!') + return redirect(url_for('show_entries')) + return render_template('login.html', error=error) + + +@app.route('/logout') +def logout(): + session.pop('logged_in', None) + session.pop('username', None) + flash('Have a nice day!') + return redirect(url_for('show_entries')) + + +@app.route('/create_user', methods=["GET", "POST"]) +def create_user(): + error = None + if request.method == 'POST': + if not request.form['password'] or not request.form['username']: + error = 'Username an password cannot be blank.' + elif Users.query.get(request.form['username']): + error = 'Username already in use.' + else: + password = md5(request.form['password'].encode('utf-8')).digest() + new_user = Users(request.form['username'], password) + db.session.add(new_user) + db.session.commit() + flash('Account successfully created!') + return redirect(url_for('login')) + return render_template('create_user.html', error=error) + + +@app.route('/add', methods=['GET', 'POST']) +def add_entry(): + if not session.get('logged_in'): + abort(401) + elif request.method == 'POST': + print(request.form['todo']) + post = ToDo(session.get('username'), request.form['todo']) + db.session.add(post) + post.due_date = datetime.strptime(request.form['date'], "%m/%d/%y") + db.session.commit() + flash('New item added to list!') + return redirect(url_for('show_entries')) + else: + return render_template('add_entry.html') + + +@app.route('/remove', methods=["POST"]) +def remove_entry(): + if not session.get('logged_in'): + abort(401) + for item in request.form.getlist('item'): + todo_update = ToDo.query.get(item) + todo_update.completed_at = datetime.utcnow() + db.session.add(todo_update) + db.session.commit() + flash('Item moved to completed list!') + return redirect(url_for('show_entries')) + + +@app.route('/done', methods=["GET"]) +def display_done(): + if not session.get('logged_in'): + return redirect(url_for('login')) + else: + done = ToDo.query.filter( + ToDo.owner==session.get('username')).filter( + ToDo.completed_at!=None).all() + for item in done: + item.completed_at = item.completed_at.strftime("%m/%d/%Y") + return render_template('display_done.html', entries=done) From ebc5a3db0d2b3377f2e1c20c73cc00766fbb7ae4 Mon Sep 17 00:00:00 2001 From: Alan Grissett Date: Thu, 19 Feb 2015 11:04:39 -0500 Subject: [PATCH 6/6] Sql alchemy, database migration --- __init__.py | 0 migrations/README | 1 + migrations/alembic.ini | 45 ++++++++++++++++++++++++ migrations/env.py | 73 +++++++++++++++++++++++++++++++++++++++ migrations/script.py.mako | 22 ++++++++++++ 5 files changed, 141 insertions(+) create mode 100644 __init__.py create mode 100755 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100755 migrations/script.py.mako diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/migrations/README b/migrations/README new file mode 100755 index 0000000..98e4f9c --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 0000000..f8ed480 --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,45 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000..70961ce --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,73 @@ +from __future__ import with_statement +from alembic import context +from sqlalchemy import engine_from_config, pool +from logging.config import fileConfig + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +from flask import current_app +config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure(url=url) + + with context.begin_transaction(): + context.run_migrations() + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + engine = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool) + + connection = engine.connect() + context.configure( + connection=connection, + target_metadata=target_metadata + ) + + try: + with context.begin_transaction(): + context.run_migrations() + finally: + connection.close() + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() + diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100755 index 0000000..9570201 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,22 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision} +Create Date: ${create_date} + +""" + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"}