From c7aa5957a0a26126966556b9231b5c4dfb1eef5b Mon Sep 17 00:00:00 2001 From: denisseai Date: Wed, 9 Jun 2021 14:54:38 -0700 Subject: [PATCH 01/13] Completed setup, added dbs, added columns to dev db --- app/__init__.py | 5 +- app/models/task.py | 5 +- app/routes.py | 62 +++++++++++++++++- migrations/README | 1 + migrations/alembic.ini | 45 +++++++++++++ migrations/env.py | 96 ++++++++++++++++++++++++++++ migrations/script.py.mako | 24 +++++++ migrations/versions/e24415e1d530_.py | 39 +++++++++++ 8 files changed, 273 insertions(+), 4 deletions(-) create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/e24415e1d530_.py diff --git a/app/__init__.py b/app/__init__.py index 2764c4cc8..8279c2db0 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,8 +1,8 @@ from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate -import os from dotenv import load_dotenv +import os db = SQLAlchemy() @@ -30,5 +30,6 @@ def create_app(test_config=None): migrate.init_app(app, db) # Register Blueprints here - + from .routes import tasks_bp + app.register_blueprint(tasks_bp) return app diff --git a/app/models/task.py b/app/models/task.py index 39c89cd16..f1a4afe38 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -3,4 +3,7 @@ class Task(db.Model): - task_id = db.Column(db.Integer, primary_key=True) + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + name = db.Column(db.String) + description = db.Column(db.String) + completed_at = db.Column(db.DateTime) diff --git a/app/routes.py b/app/routes.py index 8e9dfe684..fa9a840b0 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,2 +1,62 @@ -from flask import Blueprint +from app import db +from app.models.task import Task +from flask import request, Blueprint, make_response, jsonify +tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") + +@tasks_bp.route("", methods=["GET", "POST"]) +def handle_tasks(): + if request.method == "GET": + title_from_url = request.args.get("title") + if title_from_url: + tasks = Task.query.filter_by(title=title_from_url) + else: + tasks = Task.query.all() + tasks_response = [] + for task in tasks: + tasks_response.append({ + "id": task.id, + "title": task.title, + "description": task.description, + "completed_at": task.completed_at + }) + return jsonify(tasks_response) + + elif request.method == "POST": + request_body = request.get_json() + new_task = Task(title=request_body["title"], + description=request_body["description"], + completed_at=request_body["completed_at"]) + + db.session.add(new_task) + db.session.commit() + + return make_response(f"Task {new_task.title} successfully created", 201) + +@tasks_bp.route("/", methods=["GET", "PUT", "DELETE"]) +def handle_planet(tasks_id): + task = Task.query.get(tasks_id) + if task is None: + return make_response("something went wrong", 404) + + if request.method == "GET": + return { + "id": task.id, + "title": task.title, + "description": task.description, + "completed_at": task.completed_at + } + elif request.method == "PUT": + form_data = request.get_json() + + task.title = form_data["title"] + task.description = form_data["description"] + task.completed_at = form_data["completed_at"] + + db.session.commit() + + return make_response(f"Task #{task.id} successfully updated!!") + elif request.method == "DELETE": + db.session.delete(task) + db.session.commit() + return make_response(f"Task #{task.id} successfully deleted") \ No newline at end of file diff --git a/migrations/README b/migrations/README new file mode 100644 index 000000000..98e4f9c44 --- /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 000000000..f8ed4801f --- /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 000000000..8b3fb3353 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,96 @@ +from __future__ import with_statement + +import logging +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool +from flask import current_app + +from alembic import context + +# 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) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option( + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) +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, target_metadata=target_metadata, literal_binds=True + ) + + 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. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +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 100644 index 000000000..2c0156303 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/e24415e1d530_.py b/migrations/versions/e24415e1d530_.py new file mode 100644 index 000000000..1bf80222f --- /dev/null +++ b/migrations/versions/e24415e1d530_.py @@ -0,0 +1,39 @@ +"""empty message + +Revision ID: e24415e1d530 +Revises: +Create Date: 2021-06-09 14:50:51.876944 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e24415e1d530' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('goal', + sa.Column('goal_id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('goal_id') + ) + op.create_table('task', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('description', sa.String(), nullable=True), + sa.Column('completed_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('task') + op.drop_table('goal') + # ### end Alembic commands ### From b2fe8052741dc0f828426bac656c6026e050864a Mon Sep 17 00:00:00 2001 From: denisseai Date: Wed, 9 Jun 2021 15:06:36 -0700 Subject: [PATCH 02/13] Changed dev db's column name from name to title --- app/models/task.py | 2 +- migrations/versions/05967af878d1_.py | 30 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 migrations/versions/05967af878d1_.py diff --git a/app/models/task.py b/app/models/task.py index f1a4afe38..0f5320c81 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -4,6 +4,6 @@ class Task(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) - name = db.Column(db.String) + title = db.Column(db.String) description = db.Column(db.String) completed_at = db.Column(db.DateTime) diff --git a/migrations/versions/05967af878d1_.py b/migrations/versions/05967af878d1_.py new file mode 100644 index 000000000..d4e586001 --- /dev/null +++ b/migrations/versions/05967af878d1_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 05967af878d1 +Revises: e24415e1d530 +Create Date: 2021-06-09 15:00:48.995482 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '05967af878d1' +down_revision = 'e24415e1d530' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('title', sa.String(), nullable=True)) + op.drop_column('task', 'name') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True)) + op.drop_column('task', 'title') + # ### end Alembic commands ### From 4e834e73c96282772088448961cf73f12d00b9e8 Mon Sep 17 00:00:00 2001 From: denisseai Date: Wed, 9 Jun 2021 19:28:45 -0700 Subject: [PATCH 03/13] Tests 1-4 passing from wave 1 --- app/models/task.py | 2 +- app/routes.py | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 0f5320c81..9b86ced1b 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -6,4 +6,4 @@ class Task(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String) description = db.Column(db.String) - completed_at = db.Column(db.DateTime) + completed_at = db.Column(db.DateTime, nullable=True, default=None) diff --git a/app/routes.py b/app/routes.py index fa9a840b0..25c8b3c16 100644 --- a/app/routes.py +++ b/app/routes.py @@ -5,7 +5,7 @@ tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") @tasks_bp.route("", methods=["GET", "POST"]) -def handle_tasks(): +def handle_tasks(): if request.method == "GET": title_from_url = request.args.get("title") if title_from_url: @@ -18,15 +18,16 @@ def handle_tasks(): "id": task.id, "title": task.title, "description": task.description, - "completed_at": task.completed_at + "is_complete": bool(task.completed_at) }) return jsonify(tasks_response) elif request.method == "POST": request_body = request.get_json() - new_task = Task(title=request_body["title"], + new_task = Task(id = request_body["id"], + title=request_body["title"], description=request_body["description"], - completed_at=request_body["completed_at"]) + is_complete=request_body["completed_at"]) db.session.add(new_task) db.session.commit() @@ -37,26 +38,25 @@ def handle_tasks(): def handle_planet(tasks_id): task = Task.query.get(tasks_id) if task is None: - return make_response("something went wrong", 404) + return make_response("", 404) if request.method == "GET": - return { + selected_task = {"task":{ "id": task.id, "title": task.title, "description": task.description, - "completed_at": task.completed_at + "is_complete": bool(task.completed_at)} } + return selected_task elif request.method == "PUT": form_data = request.get_json() - task.title = form_data["title"] task.description = form_data["description"] task.completed_at = form_data["completed_at"] - db.session.commit() - return make_response(f"Task #{task.id} successfully updated!!") + elif request.method == "DELETE": db.session.delete(task) db.session.commit() - return make_response(f"Task #{task.id} successfully deleted") \ No newline at end of file + return make_response(f'Task {task.id} "{task.description}"successfully deleted') From 67548f4f8dc172b27f501912f36d865e8ff777c5 Mon Sep 17 00:00:00 2001 From: denisseai Date: Thu, 10 Jun 2021 11:28:49 -0700 Subject: [PATCH 04/13] 10 tests from wave 1 are passing --- app/routes.py | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/app/routes.py b/app/routes.py index 25c8b3c16..5a987dc6b 100644 --- a/app/routes.py +++ b/app/routes.py @@ -24,39 +24,59 @@ def handle_tasks(): elif request.method == "POST": request_body = request.get_json() - new_task = Task(id = request_body["id"], - title=request_body["title"], - description=request_body["description"], - is_complete=request_body["completed_at"]) + title = request_body.get("title") + description = request_body.get("description") + if not title or not description or "completed_at" not in request_body: + return jsonify({"details": "Invalid data"}), 400 + + new_task = Task(title=title, + description=description, + completed_at=request_body["completed_at"]) db.session.add(new_task) db.session.commit() - - return make_response(f"Task {new_task.title} successfully created", 201) + commited_task = {"task": + {"id": new_task.id, + "title": new_task.title, + "description": new_task.description, + "is_complete": bool(new_task.completed_at) + }} + + return jsonify(commited_task), 201 @tasks_bp.route("/", methods=["GET", "PUT", "DELETE"]) -def handle_planet(tasks_id): +def handle_task(tasks_id): task = Task.query.get(tasks_id) if task is None: return make_response("", 404) if request.method == "GET": - selected_task = {"task":{ - "id": task.id, + selected_task = {"task": + {"id": task.id, "title": task.title, "description": task.description, - "is_complete": bool(task.completed_at)} - } + "is_complete": bool(task.completed_at) + }} return selected_task + elif request.method == "PUT": form_data = request.get_json() + task.id = form_data["id"] task.title = form_data["title"] task.description = form_data["description"] task.completed_at = form_data["completed_at"] + + updated_task = {"task":{ + "id": task.id, + "title": task.title, + "description": task.description, + "is_complete": bool(task.completed_at)} + } db.session.commit() - return make_response(f"Task #{task.id} successfully updated!!") + return make_response(updated_task, 200) elif request.method == "DELETE": db.session.delete(task) db.session.commit() - return make_response(f'Task {task.id} "{task.description}"successfully deleted') + stuff_to_del = make_response(f'Task {task.id} "{task.description}"successfully deleted') + return jsonify({'description':{stuff_to_del}}), 200 From b7b1b66e08d9fe194116c41dbf4e9eecc009eb4a Mon Sep 17 00:00:00 2001 From: denisseai Date: Thu, 10 Jun 2021 15:33:59 -0700 Subject: [PATCH 05/13] wave 1 tests passing --- app/routes.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/app/routes.py b/app/routes.py index 5a987dc6b..aaa77ba07 100644 --- a/app/routes.py +++ b/app/routes.py @@ -11,7 +11,7 @@ def handle_tasks(): if title_from_url: tasks = Task.query.filter_by(title=title_from_url) else: - tasks = Task.query.all() + tasks = Task.query.order_by(Task.title).all() tasks_response = [] for task in tasks: tasks_response.append({ @@ -46,10 +46,7 @@ def handle_tasks(): @tasks_bp.route("/", methods=["GET", "PUT", "DELETE"]) def handle_task(tasks_id): - task = Task.query.get(tasks_id) - if task is None: - return make_response("", 404) - + task = Task.query.get_or_404(tasks_id) if request.method == "GET": selected_task = {"task": {"id": task.id, @@ -57,26 +54,25 @@ def handle_task(tasks_id): "description": task.description, "is_complete": bool(task.completed_at) }} - return selected_task + return jsonify(selected_task),200 elif request.method == "PUT": - form_data = request.get_json() - task.id = form_data["id"] - task.title = form_data["title"] - task.description = form_data["description"] - task.completed_at = form_data["completed_at"] - - updated_task = {"task":{ - "id": task.id, - "title": task.title, - "description": task.description, - "is_complete": bool(task.completed_at)} - } + request_body = request.get_json() + task.title = request_body["title"] + task.description = request_body["description"] + task.completed_at = request_body["completed_at"] + updated_task = {'task':{ + "id": task.id, + "title": task.title, + "description": task.description, + "is_complete": bool(task.completed_at) + }} db.session.commit() - return make_response(updated_task, 200) + return jsonify(updated_task),200 + elif request.method == "DELETE": db.session.delete(task) db.session.commit() - stuff_to_del = make_response(f'Task {task.id} "{task.description}"successfully deleted') - return jsonify({'description':{stuff_to_del}}), 200 + task_response_body = {"details": f'Task {task.id} "{task.title}" successfully deleted'} + return jsonify(task_response_body),200 \ No newline at end of file From b97cccd7fa938190ea4b7cd0dc35dbb1c501eedd Mon Sep 17 00:00:00 2001 From: denisseai Date: Thu, 10 Jun 2021 15:57:08 -0700 Subject: [PATCH 06/13] wave 2 tests passing --- app/routes.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/routes.py b/app/routes.py index aaa77ba07..72537091d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -5,13 +5,23 @@ tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") @tasks_bp.route("", methods=["GET", "POST"]) -def handle_tasks(): +def handle_tasks(): if request.method == "GET": title_from_url = request.args.get("title") if title_from_url: tasks = Task.query.filter_by(title=title_from_url) else: - tasks = Task.query.order_by(Task.title).all() + sort = request.args.get("sort") + if not sort: + tasks = Task.query.all() + elif sort == "asc": + tasks = Task.query.order_by(Task.title.asc()).all() + elif sort == "desc": + tasks = Task.query.order_by(Task.title.desc()).all() + else: + tasks = Task.query.all() + + # tasks = Task.query.order_by(Task.title).all() tasks_response = [] for task in tasks: tasks_response.append({ From 923f65a448b981192551c686221035e62ddae5db Mon Sep 17 00:00:00 2001 From: denisseai Date: Fri, 11 Jun 2021 16:41:56 -0700 Subject: [PATCH 07/13] wave 3 tests passing --- app/routes.py | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/app/routes.py b/app/routes.py index 72537091d..b60d9da6c 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,6 +1,7 @@ from app import db from app.models.task import Task from flask import request, Blueprint, make_response, jsonify +from datetime import datetime tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") @@ -21,7 +22,6 @@ def handle_tasks(): else: tasks = Task.query.all() - # tasks = Task.query.order_by(Task.title).all() tasks_response = [] for task in tasks: tasks_response.append({ @@ -80,9 +80,39 @@ def handle_task(tasks_id): db.session.commit() return jsonify(updated_task),200 - elif request.method == "DELETE": db.session.delete(task) db.session.commit() task_response_body = {"details": f'Task {task.id} "{task.title}" successfully deleted'} - return jsonify(task_response_body),200 \ No newline at end of file + return jsonify(task_response_body),200 + +@tasks_bp.route("//mark_complete", methods=["PATCH"]) +def handle_task_complete(task_id): + task = Task.query.get_or_404(task_id) + task.completed_at = datetime.now() + + db.session.commit() + + patched_task = {"task": { + "id": task.id, + "title": task.title, + "description": task.description, + "is_complete": True + }} + return jsonify(patched_task),200 + +@tasks_bp.route("//mark_incomplete", methods=["PATCH"]) +def handle_task_incomplete(task_id): + task = Task.query.get_or_404(task_id) + task.completed_at = None + + db.session.commit() + + patched_task = {"task": { + "id": task.id, + "title": task.title, + "description": task.description, + "is_complete": False + }} + return jsonify(patched_task),200 + From 16c49a777e2669d4ede380a4eff3ec1c5d4679cd Mon Sep 17 00:00:00 2001 From: denisseai Date: Mon, 14 Jun 2021 10:28:25 -0700 Subject: [PATCH 08/13] Created slack bot for POST --- app/routes.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/routes.py b/app/routes.py index b60d9da6c..895aeb1c5 100644 --- a/app/routes.py +++ b/app/routes.py @@ -2,6 +2,8 @@ from app.models.task import Task from flask import request, Blueprint, make_response, jsonify from datetime import datetime +import os +import requests tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") @@ -116,3 +118,14 @@ def handle_task_incomplete(task_id): }} return jsonify(patched_task),200 +def post_to_slack(text): + slack_token = os.environ.get("SLACK_TOKEN_POST") + slack_path = "https://slack.com/api/chat.postMessage" + query_params = { + "channel": "task-notification", + "text": text, + } + headers = { + "Authorization": f"Bearer {slack_token}" + } + requests.post(slack_path, params=query_params, headers=headers) \ No newline at end of file From 997033801c508d163fb7c691ae125e5277aac2af Mon Sep 17 00:00:00 2001 From: denisseai Date: Mon, 14 Jun 2021 10:47:43 -0700 Subject: [PATCH 09/13] updated model for goal --- app/models/goal.py | 1 + migrations/versions/afc38e1b3a66_.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 migrations/versions/afc38e1b3a66_.py diff --git a/app/models/goal.py b/app/models/goal.py index 8cad278f8..0bdc1731d 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -4,3 +4,4 @@ class Goal(db.Model): goal_id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) diff --git a/migrations/versions/afc38e1b3a66_.py b/migrations/versions/afc38e1b3a66_.py new file mode 100644 index 000000000..206d0de0f --- /dev/null +++ b/migrations/versions/afc38e1b3a66_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: afc38e1b3a66 +Revises: 05967af878d1 +Create Date: 2021-06-14 10:44:54.303965 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'afc38e1b3a66' +down_revision = '05967af878d1' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('goal', sa.Column('title', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('goal', 'title') + # ### end Alembic commands ### From e9335ae00ed63deed23fe949675b5cff4832f3e8 Mon Sep 17 00:00:00 2001 From: denisseai Date: Mon, 14 Jun 2021 11:06:00 -0700 Subject: [PATCH 10/13] 3 tests from wave 5 are passing --- app/models/goal.py | 2 +- app/routes.py | 29 ++++++++++++++++++++++++++- migrations/versions/268852c36865_.py | 30 ++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 migrations/versions/268852c36865_.py diff --git a/app/models/goal.py b/app/models/goal.py index 0bdc1731d..a49e3647f 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -3,5 +3,5 @@ class Goal(db.Model): - goal_id = db.Column(db.Integer, primary_key=True) + id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) diff --git a/app/routes.py b/app/routes.py index 895aeb1c5..068c386c2 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,5 +1,6 @@ from app import db from app.models.task import Task +from app.models.goal import Goal from flask import request, Blueprint, make_response, jsonify from datetime import datetime import os @@ -128,4 +129,30 @@ def post_to_slack(text): headers = { "Authorization": f"Bearer {slack_token}" } - requests.post(slack_path, params=query_params, headers=headers) \ No newline at end of file + requests.post(slack_path, params=query_params, headers=headers) + +goal_bp = Blueprint("goal_bp", __name__, url_prefix="/goals") + +@goal_bp.route("", methods=["GET", "POST"]) +def handle_goals(): + if request.method == "GET": + goals = Goal.query.all() + goals_response = [] + for goal in goals: + goals_response.append({ + "id": goal.id, + "title": goal.title + }) + return jsonify(goals_response) + elif request.method == "POST": + request_body = request.get_json() + if "title" not in request_body: + return make_response({"details": "Invalid data"}, 400) + new_goal = Goal(title=request_body["title"]) + + db.session.add(new_goal) + db.session.commit() + goal_response_body = {"goal": {"id": new_goal.id, "title": new_goal.title}} + + return jsonify(goal_response_body), 201 + diff --git a/migrations/versions/268852c36865_.py b/migrations/versions/268852c36865_.py new file mode 100644 index 000000000..42017049e --- /dev/null +++ b/migrations/versions/268852c36865_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 268852c36865 +Revises: afc38e1b3a66 +Create Date: 2021-06-14 11:01:23.686032 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '268852c36865' +down_revision = 'afc38e1b3a66' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('goal', sa.Column('id', sa.Integer(), nullable=False)) + op.drop_column('goal', 'goal_id') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('goal', sa.Column('goal_id', sa.INTEGER(), autoincrement=True, nullable=False)) + op.drop_column('goal', 'id') + # ### end Alembic commands ### From ede1db1cbb92ba34f7ace88ac24a62ed3daabded Mon Sep 17 00:00:00 2001 From: denisseai Date: Mon, 14 Jun 2021 18:57:52 -0700 Subject: [PATCH 11/13] tests for wave 5 are passing --- app/__init__.py | 3 ++- app/routes.py | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 8279c2db0..d392f914d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,6 +30,7 @@ def create_app(test_config=None): migrate.init_app(app, db) # Register Blueprints here - from .routes import tasks_bp + from .routes import tasks_bp, goal_bp app.register_blueprint(tasks_bp) + app.register_blueprint(goal_bp) return app diff --git a/app/routes.py b/app/routes.py index 068c386c2..57a8e54f4 100644 --- a/app/routes.py +++ b/app/routes.py @@ -6,6 +6,7 @@ import os import requests + tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") @tasks_bp.route("", methods=["GET", "POST"]) @@ -119,6 +120,7 @@ def handle_task_incomplete(task_id): }} return jsonify(patched_task),200 +# Slack Portion def post_to_slack(text): slack_token = os.environ.get("SLACK_TOKEN_POST") slack_path = "https://slack.com/api/chat.postMessage" @@ -131,6 +133,7 @@ def post_to_slack(text): } requests.post(slack_path, params=query_params, headers=headers) +# Goals Route Portion goal_bp = Blueprint("goal_bp", __name__, url_prefix="/goals") @goal_bp.route("", methods=["GET", "POST"]) @@ -141,13 +144,14 @@ def handle_goals(): for goal in goals: goals_response.append({ "id": goal.id, - "title": goal.title + "title": goal.title, }) - return jsonify(goals_response) + return jsonify(goals_response), 200 elif request.method == "POST": request_body = request.get_json() - if "title" not in request_body: - return make_response({"details": "Invalid data"}, 400) + title = request_body.get("title") + if not title: + return jsonify({"details": "Invalid data"}), 400 new_goal = Goal(title=request_body["title"]) db.session.add(new_goal) @@ -156,3 +160,27 @@ def handle_goals(): return jsonify(goal_response_body), 201 +@goal_bp.route("/", methods=["GET", "PUT", "DELETE"]) +def handle_goal(goal_id): + goal = Goal.query.get_or_404(goal_id) + if request.method == "GET": + selected_goal = {"goal": + {"title": goal.title, + "id": goal.id + }} + return jsonify(selected_goal), 200 + elif request.method == "PUT": + request_body = request.get_json() + goal.title = request_body["title"] + updated_goal = {'goal':{ + "id": goal.id, + "title": goal.title + }} + db.session.commit() + return jsonify(updated_goal),200 + + elif request.method == "DELETE": + db.session.delete(goal) + db.session.commit() + goal_response_body = {"details": f'Goal {goal.id} "{goal.title}" successfully deleted'} + return jsonify(goal_response_body),200 From 85482fc9808ed09d39e2d382d5374c2628b16b22 Mon Sep 17 00:00:00 2001 From: denisseai Date: Mon, 14 Jun 2021 20:48:56 -0700 Subject: [PATCH 12/13] tests for wave 6 are passing --- app/models/goal.py | 3 +- app/models/task.py | 1 + app/routes.py | 58 +++++++++++++++---- migrations/versions/05967af878d1_.py | 30 ---------- migrations/versions/268852c36865_.py | 30 ---------- migrations/versions/afc38e1b3a66_.py | 28 --------- .../{e24415e1d530_.py => b9bbd4042499_.py} | 11 ++-- 7 files changed, 58 insertions(+), 103 deletions(-) delete mode 100644 migrations/versions/05967af878d1_.py delete mode 100644 migrations/versions/268852c36865_.py delete mode 100644 migrations/versions/afc38e1b3a66_.py rename migrations/versions/{e24415e1d530_.py => b9bbd4042499_.py} (73%) diff --git a/app/models/goal.py b/app/models/goal.py index a49e3647f..6abe60396 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -3,5 +3,6 @@ class Goal(db.Model): - id = db.Column(db.Integer, primary_key=True) + goal_id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) + tasks = db.relationship("Task", backref="goal", lazy=True) diff --git a/app/models/task.py b/app/models/task.py index 9b86ced1b..2b98e6001 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -7,3 +7,4 @@ class Task(db.Model): title = db.Column(db.String) description = db.Column(db.String) completed_at = db.Column(db.DateTime, nullable=True, default=None) + goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True) diff --git a/app/routes.py b/app/routes.py index 57a8e54f4..ebca57b59 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,12 +1,11 @@ from app import db from app.models.task import Task from app.models.goal import Goal -from flask import request, Blueprint, make_response, jsonify +from flask import json, request, Blueprint, make_response, jsonify from datetime import datetime import os import requests - tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") @tasks_bp.route("", methods=["GET", "POST"]) @@ -54,15 +53,23 @@ def handle_tasks(): "title": new_task.title, "description": new_task.description, "is_complete": bool(new_task.completed_at) - }} - + }} return jsonify(commited_task), 201 @tasks_bp.route("/", methods=["GET", "PUT", "DELETE"]) def handle_task(tasks_id): task = Task.query.get_or_404(tasks_id) if request.method == "GET": - selected_task = {"task": + if task.goal_id != None: + selected_task = {"task": + {"id": task.id, + "goal_id": task.goal_id, + "title": task.title, + "description": task.description, + "is_complete": bool(task.completed_at) + }} + else: + selected_task = {"task": {"id": task.id, "title": task.title, "description": task.description, @@ -143,7 +150,7 @@ def handle_goals(): goals_response = [] for goal in goals: goals_response.append({ - "id": goal.id, + "id": goal.goal_id, "title": goal.title, }) return jsonify(goals_response), 200 @@ -156,7 +163,7 @@ def handle_goals(): db.session.add(new_goal) db.session.commit() - goal_response_body = {"goal": {"id": new_goal.id, "title": new_goal.title}} + goal_response_body = {"goal": {"id": new_goal.goal_id, "title": new_goal.title}} return jsonify(goal_response_body), 201 @@ -166,14 +173,14 @@ def handle_goal(goal_id): if request.method == "GET": selected_goal = {"goal": {"title": goal.title, - "id": goal.id + "id": goal.goal_id }} return jsonify(selected_goal), 200 elif request.method == "PUT": request_body = request.get_json() goal.title = request_body["title"] updated_goal = {'goal':{ - "id": goal.id, + "id": goal.goal_id, "title": goal.title }} db.session.commit() @@ -182,5 +189,36 @@ def handle_goal(goal_id): elif request.method == "DELETE": db.session.delete(goal) db.session.commit() - goal_response_body = {"details": f'Goal {goal.id} "{goal.title}" successfully deleted'} + goal_response_body = {"details": f'Goal {goal.goal_id} "{goal.title}" successfully deleted'} return jsonify(goal_response_body),200 + +@goal_bp.route("//tasks", methods=["GET", "POST"]) +def handle_goals_and_tasks(goal_id): + if request.method == "POST": + goal = Goal.query.get_or_404(goal_id) + request_body = request.get_json() + for id in request_body["task_ids"]: + task = Task.query.get(id) + goal.tasks.append(task) + db.session.add(goal) + db.session.commit() + + goal_task_response_body = {"id": goal.goal_id, "task_ids": request_body["task_ids"]} + return jsonify(goal_task_response_body), 200 + + elif request.method == "GET": + goal = Goal.query.get_or_404(goal_id) + tasks = goal.tasks + list_of_tasks = [] + + for task in tasks: + individual_task = { + "id": task.id, + "goal_id": goal.goal_id, + "title": task.title, + "description": task.description, + "is_complete": bool(task.completed_at) + } + list_of_tasks.append(individual_task) + goal_task_get_response_body = {"id": goal.goal_id, "title": goal.title,"tasks": list_of_tasks} + return jsonify(goal_task_get_response_body), 200 diff --git a/migrations/versions/05967af878d1_.py b/migrations/versions/05967af878d1_.py deleted file mode 100644 index d4e586001..000000000 --- a/migrations/versions/05967af878d1_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""empty message - -Revision ID: 05967af878d1 -Revises: e24415e1d530 -Create Date: 2021-06-09 15:00:48.995482 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '05967af878d1' -down_revision = 'e24415e1d530' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('title', sa.String(), nullable=True)) - op.drop_column('task', 'name') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True)) - op.drop_column('task', 'title') - # ### end Alembic commands ### diff --git a/migrations/versions/268852c36865_.py b/migrations/versions/268852c36865_.py deleted file mode 100644 index 42017049e..000000000 --- a/migrations/versions/268852c36865_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""empty message - -Revision ID: 268852c36865 -Revises: afc38e1b3a66 -Create Date: 2021-06-14 11:01:23.686032 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '268852c36865' -down_revision = 'afc38e1b3a66' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('goal', sa.Column('id', sa.Integer(), nullable=False)) - op.drop_column('goal', 'goal_id') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('goal', sa.Column('goal_id', sa.INTEGER(), autoincrement=True, nullable=False)) - op.drop_column('goal', 'id') - # ### end Alembic commands ### diff --git a/migrations/versions/afc38e1b3a66_.py b/migrations/versions/afc38e1b3a66_.py deleted file mode 100644 index 206d0de0f..000000000 --- a/migrations/versions/afc38e1b3a66_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: afc38e1b3a66 -Revises: 05967af878d1 -Create Date: 2021-06-14 10:44:54.303965 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'afc38e1b3a66' -down_revision = '05967af878d1' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('goal', sa.Column('title', sa.String(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('goal', 'title') - # ### end Alembic commands ### diff --git a/migrations/versions/e24415e1d530_.py b/migrations/versions/b9bbd4042499_.py similarity index 73% rename from migrations/versions/e24415e1d530_.py rename to migrations/versions/b9bbd4042499_.py index 1bf80222f..899a23352 100644 --- a/migrations/versions/e24415e1d530_.py +++ b/migrations/versions/b9bbd4042499_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: e24415e1d530 +Revision ID: b9bbd4042499 Revises: -Create Date: 2021-06-09 14:50:51.876944 +Create Date: 2021-06-14 19:58:08.425060 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = 'e24415e1d530' +revision = 'b9bbd4042499' down_revision = None branch_labels = None depends_on = None @@ -20,13 +20,16 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.create_table('goal', sa.Column('goal_id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(), nullable=True), sa.PrimaryKeyConstraint('goal_id') ) op.create_table('task', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('name', sa.String(), nullable=True), + sa.Column('title', sa.String(), nullable=True), sa.Column('description', sa.String(), nullable=True), sa.Column('completed_at', sa.DateTime(), nullable=True), + sa.Column('goal_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['goal_id'], ['goal.goal_id'], ), sa.PrimaryKeyConstraint('id') ) # ### end Alembic commands ### From 4fa8990d5f02875e5131af2947513764c58a6860 Mon Sep 17 00:00:00 2001 From: denisseai Date: Mon, 14 Jun 2021 21:06:05 -0700 Subject: [PATCH 13/13] Added Procfile for Heroku --- Procfile | 1 + 1 file changed, 1 insertion(+) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..62e430aca --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: gunicorn 'app:create_app()' \ No newline at end of file