From 43ee5fd3c1bcdcea666ffa286d38ec817b825cfb Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sun, 7 May 2023 21:08:31 -0700 Subject: [PATCH 01/86] Established tasks_bp blueprint in app routes.py module. --- app/routes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index 3aae38d49..ae2da5b9a 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1 +1,3 @@ -from flask import Blueprint \ No newline at end of file +from flask import Blueprint + +tasks_bp = Blueprint('tasks_bp', __name__, url_prefix='/tasks') From fb42416f036432ebcc66510bfe592962b7a60931 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sun, 7 May 2023 21:10:24 -0700 Subject: [PATCH 02/86] Imported and regiestered tasks_bp Blueprints in app __init__.py module. --- app/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index 2764c4cc8..30052751d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,5 +30,7 @@ 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 From 702295d799859f9d9389b1af2347e4bce00be79e Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sun, 7 May 2023 21:40:32 -0700 Subject: [PATCH 03/86] Added Task model and created associated columns for task table within the task.py module. --- app/models/task.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/task.py b/app/models/task.py index c91ab281f..cdce4689f 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -2,4 +2,8 @@ class Task(db.Model): - task_id = db.Column(db.Integer, primary_key=True) + task_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + title = db.Column(db.String, nullable=False) + description = db.Column(db.String, nullable=False) + completed_at = db.Column(db.DateTime, nullable=True) + is_complete = db.Column(db.Boolean) From c6a044d44b6df271f9242af71059039df7a8aaad Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sun, 7 May 2023 21:41:42 -0700 Subject: [PATCH 04/86] Generated migrations folder and necessary alembic, env, etc. files to track migration versions. --- migrations/README | 1 + migrations/alembic.ini | 45 +++++++++ migrations/env.py | 96 +++++++++++++++++++ migrations/script.py.mako | 24 +++++ ...dded_task_model_and_created_associated_.py | 40 ++++++++ 5 files changed, 206 insertions(+) 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/f1bf59b1ec0f_added_task_model_and_created_associated_.py 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/f1bf59b1ec0f_added_task_model_and_created_associated_.py b/migrations/versions/f1bf59b1ec0f_added_task_model_and_created_associated_.py new file mode 100644 index 000000000..07eb72dab --- /dev/null +++ b/migrations/versions/f1bf59b1ec0f_added_task_model_and_created_associated_.py @@ -0,0 +1,40 @@ +"""Added Task model and created associated columns for task table. + +Revision ID: f1bf59b1ec0f +Revises: +Create Date: 2023-05-07 21:37:25.508202 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f1bf59b1ec0f' +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('task_id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.String(), nullable=False), + sa.Column('description', sa.String(), nullable=False), + sa.Column('completed_at', sa.DateTime(), nullable=True), + sa.Column('is_complete', sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint('task_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 a122ccf9714b285cd1f9775a4422a6cb03891ed0 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sun, 7 May 2023 21:45:06 -0700 Subject: [PATCH 05/86] Wrote to_dict helper function within model task.py module in order to format HTTP responses into dictionaries. --- app/models/task.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/models/task.py b/app/models/task.py index cdce4689f..2d86c6480 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -7,3 +7,11 @@ class Task(db.Model): description = db.Column(db.String, nullable=False) completed_at = db.Column(db.DateTime, nullable=True) is_complete = db.Column(db.Boolean) + +def to_dict(self): + return { + "id": self.task_id, + "title": self.title, + "description": self.description, + "is_complete": self.is_complete + } \ No newline at end of file From e651e96ef26d998071ee71b8dc579502543881d2 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sun, 7 May 2023 22:00:26 -0700 Subject: [PATCH 06/86] Established all necessary imports in app routes.py module. --- app/routes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index ae2da5b9a..465ad2b44 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,3 +1,5 @@ -from flask import Blueprint +from app import db +from app.models.task import Task +from flask import Blueprint, jsonify, make_response, request, abort tasks_bp = Blueprint('tasks_bp', __name__, url_prefix='/tasks') From 32f1fb1f05b37b1744ae87f8de5765354d0f4950 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sun, 7 May 2023 22:18:03 -0700 Subject: [PATCH 07/86] Wrote POST endpoint and create_task function in app routes.py module. --- app/routes.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index 465ad2b44..dda7bb8eb 100644 --- a/app/routes.py +++ b/app/routes.py @@ -2,4 +2,19 @@ from app.models.task import Task from flask import Blueprint, jsonify, make_response, request, abort -tasks_bp = Blueprint('tasks_bp', __name__, url_prefix='/tasks') + +tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") + +@tasks_bp.route("", methods=["POST"]) +def create_task(): + request_body = request.get_json() + new_task = Task( + title = request_body["title"], + description = request_body["description"], + is_complete = request_body["is_complete"] + ) + + db.session.add(new_task) + db.session.commit() + + return make_response(f"Task {new_task.title} has been successfully created. Hurray!", 201) \ No newline at end of file From 7750431301a7402c47b843c81725cd59e0d0230b Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sun, 7 May 2023 22:36:26 -0700 Subject: [PATCH 08/86] Updated create_task funciton to return invalid data and 400 HTTP response if title, description, or completed_at request is missing. --- app/routes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/routes.py b/app/routes.py index dda7bb8eb..e69a83425 100644 --- a/app/routes.py +++ b/app/routes.py @@ -8,6 +8,10 @@ @tasks_bp.route("", methods=["POST"]) def create_task(): request_body = request.get_json() + + if "title" not in request_body or "description" not in request_body or "completed_at" not in request_body: + return make_response({"details": "Invalid data"}, 400) + new_task = Task( title = request_body["title"], description = request_body["description"], From 75f10d9c0a438fcccfcc61d5484e6083b8f5fc6a Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 00:34:01 -0700 Subject: [PATCH 09/86] Wrote GET endpoint and read_all_tasks function in app routes.py module. --- app/routes.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index e69a83425..aa23f67c9 100644 --- a/app/routes.py +++ b/app/routes.py @@ -21,4 +21,19 @@ def create_task(): db.session.add(new_task) db.session.commit() - return make_response(f"Task {new_task.title} has been successfully created. Hurray!", 201) \ No newline at end of file + return make_response(f"Task {new_task.title} has been successfully created. Hurray!", 201) + +@tasks_bp.route("", methods=["GET"]) +def read_all_tasks(): + tasks_response = [] + tasks = Task.query.all() + for task in tasks: + tasks_response.append( + { + "id": task.task_id, + "title": task.title, + "description": task.description, + "is_complete": False + } + ) + return make_response(jsonify(tasks_response), 200) \ No newline at end of file From 6f32476ca13aebb8a5c35df5d85d3e41c5ca3b2c Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 00:45:34 -0700 Subject: [PATCH 10/86] Refactored read_all_tasks function in app routes.py module. --- app/routes.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/app/routes.py b/app/routes.py index aa23f67c9..6523e5d2c 100644 --- a/app/routes.py +++ b/app/routes.py @@ -25,15 +25,7 @@ def create_task(): @tasks_bp.route("", methods=["GET"]) def read_all_tasks(): - tasks_response = [] tasks = Task.query.all() - for task in tasks: - tasks_response.append( - { - "id": task.task_id, - "title": task.title, - "description": task.description, - "is_complete": False - } - ) - return make_response(jsonify(tasks_response), 200) \ No newline at end of file + all_tasks = [task.to_dict() for task in tasks] + + return jsonify(all_tasks), 200 \ No newline at end of file From 8e701a77b5c6a0ab16af13428b4c8c1ae1062bdb Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 00:52:43 -0700 Subject: [PATCH 11/86] Removed 400 response if completed_at is not in the body request. --- app/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index 6523e5d2c..fa1f9a754 100644 --- a/app/routes.py +++ b/app/routes.py @@ -9,7 +9,7 @@ def create_task(): request_body = request.get_json() - if "title" not in request_body or "description" not in request_body or "completed_at" not in request_body: + if "title" not in request_body or "description" not in request_body: return make_response({"details": "Invalid data"}, 400) new_task = Task( From 0f1c425f2b56044236c3656a913801011e4b5d05 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 01:11:34 -0700 Subject: [PATCH 12/86] Wrote GET endpoint for single task and read_one_task function in app routes.py module. --- app/routes.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index fa1f9a754..49215e648 100644 --- a/app/routes.py +++ b/app/routes.py @@ -28,4 +28,10 @@ def read_all_tasks(): tasks = Task.query.all() all_tasks = [task.to_dict() for task in tasks] - return jsonify(all_tasks), 200 \ No newline at end of file + return jsonify(all_tasks), 200 + +@tasks_bp.route("/", methods=["GET"]) +def read_one_task(task_id): + task = validate_model(Task, task_id) + + return make_response({"task": task.to_dict()}, 200) \ No newline at end of file From 099312bf4633f744ec71025dd770267ed86d3782 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 01:18:51 -0700 Subject: [PATCH 13/86] Wrote validate_model function in app routes.py module. --- app/routes.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/routes.py b/app/routes.py index 49215e648..1d03da2b1 100644 --- a/app/routes.py +++ b/app/routes.py @@ -5,6 +5,18 @@ tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") +def validate_model(task_id): + try: + task_id = int(task_id) + except: + abort(make_response({"message": f"Task {task_id} is invalid"}, 400)) + + task = Task.query.get(task_id) + + if not task: + abort(make_response({"message": f"Task {task_id} not found"}, 404)) + + @tasks_bp.route("", methods=["POST"]) def create_task(): request_body = request.get_json() From caad9c0750bfa8e97b69c97e21601a7cca319d98 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 01:24:16 -0700 Subject: [PATCH 14/86] Wrote PUT endpoint and update_task function in app routes.py module. --- app/routes.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/routes.py b/app/routes.py index 1d03da2b1..9329fa098 100644 --- a/app/routes.py +++ b/app/routes.py @@ -46,4 +46,16 @@ def read_all_tasks(): def read_one_task(task_id): task = validate_model(Task, task_id) + return make_response({"task": task.to_dict()}, 200) + +@tasks_bp.route("/", methods=["PUT"]) +def update_task(task_id): + task = validate_model(Task, task_id) + request_body = request.get_json() + + task.title = request_body["title"] + task.description = request_body["description"] + + db.session.commit() + return make_response({"task": task.to_dict()}, 200) \ No newline at end of file From 38252f17079824834388ef9b95371eaa2f3bf4fe Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 01:29:53 -0700 Subject: [PATCH 15/86] Wrote DELETE endpoint and delete_task funciton in app routes.py module. --- app/routes.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index 9329fa098..5f139ca64 100644 --- a/app/routes.py +++ b/app/routes.py @@ -58,4 +58,13 @@ def update_task(task_id): db.session.commit() - return make_response({"task": task.to_dict()}, 200) \ No newline at end of file + return make_response({"task": task.to_dict()}, 200) + +@tasks_bp.route("/", methods=["DELETE"]) +def delete_task(task_id): + task = validate_model(Task, task_id) + + db.session.delete(task) + db.session.commit() + + return make_response({"details": f"Task {task.task_id} \"{task.title}\" successfully deleted"}), 200 \ No newline at end of file From d1d62c100a48f997d97a79fa169bf444e6291c2d Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 02:04:41 -0700 Subject: [PATCH 16/86] Fixed indentation and reformatted to_dict helper function in model Task class. --- app/models/task.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 2d86c6480..729d73e12 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -8,10 +8,10 @@ class Task(db.Model): completed_at = db.Column(db.DateTime, nullable=True) is_complete = db.Column(db.Boolean) -def to_dict(self): - return { - "id": self.task_id, - "title": self.title, - "description": self.description, - "is_complete": self.is_complete - } \ No newline at end of file + def to_dict(self): + return dict( + id=self.task_id, + title=self.title, + description=self.description, + is_complete=self.is_complete + ) \ No newline at end of file From b9d3db3cfec0c4fd0e997874f28a436869739311 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 02:11:35 -0700 Subject: [PATCH 17/86] Added missing return statement in validate_model function in app routes.py module. --- app/routes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/routes.py b/app/routes.py index 5f139ca64..7ecf16446 100644 --- a/app/routes.py +++ b/app/routes.py @@ -15,6 +15,8 @@ def validate_model(task_id): if not task: abort(make_response({"message": f"Task {task_id} not found"}, 404)) + + return task @tasks_bp.route("", methods=["POST"]) From 00f641c06a0d2ccb82117b6346f83e35f19940e4 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 02:21:26 -0700 Subject: [PATCH 18/86] Fixed AttributeError by eliminating unecessary parameter in endpoints defined within the app routes.py module. --- app/routes.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/routes.py b/app/routes.py index 7ecf16446..b053d28fd 100644 --- a/app/routes.py +++ b/app/routes.py @@ -18,7 +18,6 @@ def validate_model(task_id): return task - @tasks_bp.route("", methods=["POST"]) def create_task(): request_body = request.get_json() @@ -46,13 +45,13 @@ def read_all_tasks(): @tasks_bp.route("/", methods=["GET"]) def read_one_task(task_id): - task = validate_model(Task, task_id) + task = validate_model(task_id) - return make_response({"task": task.to_dict()}, 200) + return task.to_dict(), 200 @tasks_bp.route("/", methods=["PUT"]) def update_task(task_id): - task = validate_model(Task, task_id) + task = validate_model(task_id) request_body = request.get_json() task.title = request_body["title"] @@ -64,7 +63,7 @@ def update_task(task_id): @tasks_bp.route("/", methods=["DELETE"]) def delete_task(task_id): - task = validate_model(Task, task_id) + task = validate_model(task_id) db.session.delete(task) db.session.commit() From c3789b26e5138440f349ad9daa37475f5db2ed4a Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 02:33:32 -0700 Subject: [PATCH 19/86] Added completed_as attribute in to_dict helper function within model Task class. --- app/models/task.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/task.py b/app/models/task.py index 729d73e12..ab9c35ad8 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -13,5 +13,6 @@ def to_dict(self): id=self.task_id, title=self.title, description=self.description, + completed_at=self.completed_at, is_complete=self.is_complete ) \ No newline at end of file From cf6e15d5b5a9e1ea03914e7ec728c18d049a096f Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 02:35:08 -0700 Subject: [PATCH 20/86] Changed response body in POST endpoint to return completed_at instead of is_complete record in response body. --- app/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index b053d28fd..d8303141d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -28,7 +28,7 @@ def create_task(): new_task = Task( title = request_body["title"], description = request_body["description"], - is_complete = request_body["is_complete"] + completed_at = request_body["completed_at"] ) db.session.add(new_task) From 8b7c0a1486865d40e3d38c1e76ba752f8d0a0ce1 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 05:52:53 -0700 Subject: [PATCH 21/86] Completed test_get_task_not_found assertion in wave_01 module. --- tests/test_wave_01.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index dca626d78..e6889543a 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -2,7 +2,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_no_saved_tasks(client): # Act response = client.get("/tasks") @@ -13,7 +13,7 @@ def test_get_tasks_no_saved_tasks(client): assert response_body == [] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_one_saved_tasks(client, one_task): # Act response = client.get("/tasks") @@ -59,8 +59,9 @@ def test_get_task_not_found(client): # Assert assert response.status_code == 404 + assert response_body == {f"message": "Task 1 not found"} - raise Exception("Complete test with assertion about response body") + #raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** From 426efd3638b5dc1dbdc1ffcce7b088ba06ff1fc1 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 05:55:22 -0700 Subject: [PATCH 22/86] Completed test_update_task_not_found assertion in wave_01 module. --- tests/test_wave_01.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index e6889543a..be35fefbb 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -131,8 +131,9 @@ def test_update_task_not_found(client): # Assert assert response.status_code == 404 + assert response_body == {f"message": "Task 1 not found"} - raise Exception("Complete test with assertion about response body") + # raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** From 701210a333d078b445f483adf17e44c7723f3b9a Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 8 May 2023 05:56:54 -0700 Subject: [PATCH 23/86] Completed test_delete_task_not_found in wave_01 module. --- tests/test_wave_01.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index be35fefbb..c5d0c9dfd 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -162,8 +162,9 @@ def test_delete_task_not_found(client): # Assert assert response.status_code == 404 + assert response_body == {f"message": "Task 1 not found"} - raise Exception("Complete test with assertion about response body") + #raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** From affd1ef6b48ba851c32e90bab03d07d8ed053dd5 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Wed, 10 May 2023 15:56:47 -0700 Subject: [PATCH 24/86] Wrote @classmethod from_dict helper function in app method task.py module. --- app/models/task.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index ab9c35ad8..0ac7c5b54 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -9,10 +9,25 @@ class Task(db.Model): is_complete = db.Column(db.Boolean) def to_dict(self): + random_bool = True + if self.is_complete == True: + random_bool = True + else: + random_bool = False return dict( id=self.task_id, title=self.title, description=self.description, - completed_at=self.completed_at, - is_complete=self.is_complete - ) \ No newline at end of file + # In the Wave 1 test, they don't want completed_at in the response + # completed_at=self.completed_at, + # In the Wave 1 test, it wants is_complete to come back as True/False... in the db, this might be stored as "None" depending on the task + is_complete=random_bool + ) + + @classmethod + def from_dict(cls, task_data): + new_task = Task( + title=task_data["title"], + description=task_data["description"] + ) + return new_task \ No newline at end of file From 01515dad7ae580b9453b26941c44017116781279 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Wed, 10 May 2023 16:02:33 -0700 Subject: [PATCH 25/86] Refactored create_task function (within the app routes.py modeul) to reference from_dict helper function. --- app/routes.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/routes.py b/app/routes.py index d8303141d..a1521ec87 100644 --- a/app/routes.py +++ b/app/routes.py @@ -25,16 +25,12 @@ def create_task(): if "title" not in request_body or "description" not in request_body: return make_response({"details": "Invalid data"}, 400) - new_task = Task( - title = request_body["title"], - description = request_body["description"], - completed_at = request_body["completed_at"] - ) + new_task = Task.from_dict(request_body) db.session.add(new_task) db.session.commit() - return make_response(f"Task {new_task.title} has been successfully created. Hurray!", 201) + return make_response({"task": new_task.to_dict()}, 201) @tasks_bp.route("", methods=["GET"]) def read_all_tasks(): From 8a539181f36fe9a3335382ae1be4a833c2158f06 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Wed, 10 May 2023 16:16:41 -0700 Subject: [PATCH 26/86] Updated return statement in read_one_task function (within the app routes.py module) to include 'task' in response body. --- app/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index a1521ec87..84db8f749 100644 --- a/app/routes.py +++ b/app/routes.py @@ -43,7 +43,7 @@ def read_all_tasks(): def read_one_task(task_id): task = validate_model(task_id) - return task.to_dict(), 200 + return make_response({"task": task.to_dict()}, 200) @tasks_bp.route("/", methods=["PUT"]) def update_task(task_id): From a92765c2c25480b980dc8fa74e35b3543dfe2595 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Wed, 10 May 2023 16:22:40 -0700 Subject: [PATCH 27/86] Imported asc, desc features from sqlalchemy in app routes.py module. --- app/routes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/routes.py b/app/routes.py index 84db8f749..ca1797d70 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 Blueprint, jsonify, make_response, request, abort +from sqlalchemy import asc, desc tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") @@ -18,6 +19,10 @@ def validate_model(task_id): return task +# ----------------------------------- +# ROUTES +# ----------------------------------- + @tasks_bp.route("", methods=["POST"]) def create_task(): request_body = request.get_json() From 5a22ed35172d764a5d603bb3b9c035af41be6bd8 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Wed, 10 May 2023 16:52:02 -0700 Subject: [PATCH 28/86] Added sorting capability to read_all_tasks function in app routes.py. --- app/routes.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index ca1797d70..57b17f4e7 100644 --- a/app/routes.py +++ b/app/routes.py @@ -39,7 +39,15 @@ def create_task(): @tasks_bp.route("", methods=["GET"]) def read_all_tasks(): - tasks = Task.query.all() + sort_query = request.args.get("sort") + if sort_query: + if sort_query == "asc": + tasks = Task.query.order_by(Task.title.asc()).all() + elif sort_query == "desc": + tasks = Task.query.order_by(Task.title.desc()).all() + else: + tasks = Task.query.all() + all_tasks = [task.to_dict() for task in tasks] return jsonify(all_tasks), 200 From b99a25ee391c68bdb949b6a7eb934e6e556595f3 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Wed, 10 May 2023 17:39:39 -0700 Subject: [PATCH 29/86] Update class Task and to_dict helper function to account for task completions in app model task.py module. --- app/models/task.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 0ac7c5b54..e1f551178 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -9,19 +9,27 @@ class Task(db.Model): is_complete = db.Column(db.Boolean) def to_dict(self): - random_bool = True - if self.is_complete == True: - random_bool = True + if self.completed_at: + return dict( + id=self.task_id, + title=self.title, + description=self.description, + is_complete=True + ) + + if not self.completed_at: + return dict( + id=self.task_id, + title=self.title, + description=self.description, + is_complete=False + ) else: - random_bool = False - return dict( - id=self.task_id, - title=self.title, - description=self.description, - # In the Wave 1 test, they don't want completed_at in the response - # completed_at=self.completed_at, - # In the Wave 1 test, it wants is_complete to come back as True/False... in the db, this might be stored as "None" depending on the task - is_complete=random_bool + return dict( + id=self.task_id, + title=self.title, + description=self.description, + is_complete=False ) @classmethod From ae015360cb0e79d2b0de20df8189664994d8df6e Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Wed, 10 May 2023 17:47:41 -0700 Subject: [PATCH 30/86] Wrote PATCH endpoint and mark_task_incomplete function in app routes.py module. --- app/routes.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/routes.py b/app/routes.py index 57b17f4e7..1e116b823 100644 --- a/app/routes.py +++ b/app/routes.py @@ -70,6 +70,14 @@ def update_task(task_id): return make_response({"task": task.to_dict()}, 200) +@tasks_bp.route("//mark_incomplete", methods=["PATCH"]) +def mark_task_incomplete(task_id): + task = validate_model(Task, task_id) + task.completed_at = None + db.session.commit() + + return make_response({"task": task.to_dict()}, 200) + @tasks_bp.route("/", methods=["DELETE"]) def delete_task(task_id): task = validate_model(task_id) From f369be75703f9af69f8e6e171aafc96ea328feff Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Wed, 10 May 2023 18:00:27 -0700 Subject: [PATCH 31/86] Imported datetime in app routes.py module. --- app/routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/routes.py b/app/routes.py index 1e116b823..de71af8bc 100644 --- a/app/routes.py +++ b/app/routes.py @@ -2,6 +2,7 @@ from app.models.task import Task from flask import Blueprint, jsonify, make_response, request, abort from sqlalchemy import asc, desc +from datetime import datetime tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") From 104044ccfd076537ba5e335dec1f8194c98c53bb Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Wed, 10 May 2023 18:01:39 -0700 Subject: [PATCH 32/86] Wrote PATCH endpoint and mark_task_complete function in app routes.py module. --- app/routes.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/routes.py b/app/routes.py index de71af8bc..7572ad387 100644 --- a/app/routes.py +++ b/app/routes.py @@ -71,6 +71,15 @@ def update_task(task_id): return make_response({"task": task.to_dict()}, 200) +@tasks_bp.route("//mark_complete", methods=["PATCH"]) +def mark_task_complete(task_id): + task = validate_model(Task, task_id) + + task.completed_at = datetime.utcnow() + db.session.commit() + + return make_response({"task": task.to_dict()}, 200) + @tasks_bp.route("//mark_incomplete", methods=["PATCH"]) def mark_task_incomplete(task_id): task = validate_model(Task, task_id) From 6047864178d420a083166a9b733915da29a4c3ca Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Wed, 10 May 2023 18:23:31 -0700 Subject: [PATCH 33/86] Completed assertion for test_mark_complete_missing_task function in test_wave_03.py module. --- tests/test_wave_03.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index 32d379822..23cfa57c6 100644 --- a/tests/test_wave_03.py +++ b/tests/test_wave_03.py @@ -5,7 +5,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_on_incomplete_task(client, one_task): # Arrange """ @@ -42,7 +42,7 @@ def test_mark_complete_on_incomplete_task(client, one_task): assert Task.query.get(1).completed_at -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_mark_incomplete_on_complete_task(client, completed_task): # Act response = client.patch("/tasks/1/mark_incomplete") @@ -62,7 +62,7 @@ def test_mark_incomplete_on_complete_task(client, completed_task): assert Task.query.get(1).completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_on_completed_task(client, completed_task): # Arrange """ @@ -99,7 +99,7 @@ def test_mark_complete_on_completed_task(client, completed_task): assert Task.query.get(1).completed_at -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_mark_incomplete_on_incomplete_task(client, one_task): # Act response = client.patch("/tasks/1/mark_incomplete") @@ -119,7 +119,7 @@ def test_mark_incomplete_on_incomplete_task(client, one_task): assert Task.query.get(1).completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_missing_task(client): # Act response = client.patch("/tasks/1/mark_complete") @@ -127,8 +127,9 @@ def test_mark_complete_missing_task(client): # Assert assert response.status_code == 404 + assert response_body == {f"message": "Task 1 not found"} - raise Exception("Complete test with assertion about response body") + #raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** From c996f2750643f302b4da077f6831d5e8665b22ef Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Wed, 10 May 2023 18:29:36 -0700 Subject: [PATCH 34/86] Completed assertion for test_mark_incomplete_missing_task function in test_wave_03.py module. --- tests/test_wave_03.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index 23cfa57c6..6299b2668 100644 --- a/tests/test_wave_03.py +++ b/tests/test_wave_03.py @@ -143,8 +143,9 @@ def test_mark_incomplete_missing_task(client): # Assert assert response.status_code == 404 + assert response_body == {f"message": "Task 1 not found"} - raise Exception("Complete test with assertion about response body") + #raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** From 29ad072299b84ec43fce879000835d38af8e148f Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 01:28:19 -0700 Subject: [PATCH 35/86] Imported os, requests in app routes.py module. --- app/routes.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/routes.py b/app/routes.py index 7572ad387..17b9abb5b 100644 --- a/app/routes.py +++ b/app/routes.py @@ -3,6 +3,7 @@ from flask import Blueprint, jsonify, make_response, request, abort from sqlalchemy import asc, desc from datetime import datetime +import os, requests tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") @@ -73,7 +74,7 @@ def update_task(task_id): @tasks_bp.route("//mark_complete", methods=["PATCH"]) def mark_task_complete(task_id): - task = validate_model(Task, task_id) + task = validate_model(task_id) task.completed_at = datetime.utcnow() db.session.commit() @@ -82,7 +83,8 @@ def mark_task_complete(task_id): @tasks_bp.route("//mark_incomplete", methods=["PATCH"]) def mark_task_incomplete(task_id): - task = validate_model(Task, task_id) + task = validate_model(task_id) + task.completed_at = None db.session.commit() From 3658a196b52faab44d3494d32f937ee6f50bd665 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 01:36:02 -0700 Subject: [PATCH 36/86] Wrote slack_bot_message helper function in app routes.py module. --- app/routes.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/routes.py b/app/routes.py index 17b9abb5b..bd9759913 100644 --- a/app/routes.py +++ b/app/routes.py @@ -21,6 +21,18 @@ def validate_model(task_id): return task +def slack_bot_message(message): + slack_api_key = os.environ.get("SLACK_BOT_TOKEN") + slack_url = "https://slack.com/api/chat.postMessage" + header = {"Authorization": slack_api_key} + + slack_query_params = { + "channel": "task-notifications", + "text": message + } + print(slack_api_key) + requests.post(url=slack_url, data=slack_query_params, headers=header) + # ----------------------------------- # ROUTES # ----------------------------------- From 1a1390aed70cfcc2a7fb458ce5338335605b6ae7 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 01:40:46 -0700 Subject: [PATCH 37/86] Updated mark_task_complete function to call Slack API and display Slack message in app routes.py module. --- app/routes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/routes.py b/app/routes.py index bd9759913..db11a3fae 100644 --- a/app/routes.py +++ b/app/routes.py @@ -91,6 +91,8 @@ def mark_task_complete(task_id): task.completed_at = datetime.utcnow() db.session.commit() + slack_bot_message(f"Someone just completed the task {task.title}") + return make_response({"task": task.to_dict()}, 200) @tasks_bp.route("//mark_incomplete", methods=["PATCH"]) From e24446585f3fb84780354726c119362ef435f656 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 02:06:51 -0700 Subject: [PATCH 38/86] Added title attribute to class Goal in app model goal.py module. --- app/models/goal.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/goal.py b/app/models/goal.py index b0ed11dd8..14d686364 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -3,3 +3,5 @@ class Goal(db.Model): goal_id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) + From 57b686bfd887b366eef4a55742e318e0cbbb53ff Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 02:16:51 -0700 Subject: [PATCH 39/86] Establish bidirectional relationship in goal.py module to task database using relationship.back_populates parameter. --- app/models/goal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/goal.py b/app/models/goal.py index 14d686364..200578c43 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -4,4 +4,5 @@ class Goal(db.Model): goal_id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) - + tasks = db.relationship("Task", back_populates="goal", lazy=True) + From 6b8422f6b1f69aabbe4fe1839aa2c567b8f5a50d Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 02:22:20 -0700 Subject: [PATCH 40/86] Establish bidirectional relationship in task.py module to goal database using relationship.back_populates parameter. --- app/models/task.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/task.py b/app/models/task.py index e1f551178..f4ad9e8c7 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -7,6 +7,7 @@ class Task(db.Model): description = db.Column(db.String, nullable=False) completed_at = db.Column(db.DateTime, nullable=True) is_complete = db.Column(db.Boolean) + goal = db.relationship("Goal", back_populates="tasks") def to_dict(self): if self.completed_at: From bb94cd1e63eff178f330e51d16b170589017d659 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 02:27:12 -0700 Subject: [PATCH 41/86] Linked goal and task db by adding goal_id column as foreign key to task table in app model task.py module. --- app/models/task.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/task.py b/app/models/task.py index f4ad9e8c7..c03924d5c 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -7,6 +7,7 @@ class Task(db.Model): description = db.Column(db.String, nullable=False) completed_at = db.Column(db.DateTime, nullable=True) is_complete = db.Column(db.Boolean) + goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True) goal = db.relationship("Goal", back_populates="tasks") def to_dict(self): From 7a323ea33485dee66b19cf8ada7dd9d6e77362ea Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 02:29:30 -0700 Subject: [PATCH 42/86] Wrote to_dict helper function within class Goal of app models goal.py module. --- app/models/goal.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/goal.py b/app/models/goal.py index 200578c43..509060885 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -6,3 +6,8 @@ class Goal(db.Model): title = db.Column(db.String) tasks = db.relationship("Task", back_populates="goal", lazy=True) +def to_dict(self): + return dict( + id=self.goal_id, + title=self.title + ) From 24b4b163c76db4545ee8de7b20267828a8221d06 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 02:39:22 -0700 Subject: [PATCH 43/86] Changed registered blueprint source from .routes to app.models.task for importing tasks_bp in dunder init module. --- app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 30052751d..e27927edf 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,7 +30,7 @@ def create_app(test_config=None): migrate.init_app(app, db) # Register Blueprints here - from .routes import tasks_bp + from app.models.task import tasks_bp app.register_blueprint(tasks_bp) return app From baf645057af790a1fb854051865cddc2032c5341 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 02:40:38 -0700 Subject: [PATCH 44/86] Registered blueprints for goals_bp in app dunder init module. --- app/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index e27927edf..e36c9f37d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -33,4 +33,7 @@ def create_app(test_config=None): from app.models.task import tasks_bp app.register_blueprint(tasks_bp) + from app.models.goal import goals_bp + app.register_blueprint(goals_bp) + return app From 5ba683b3f0a99c5d0f48b9f7e425f38e8c71dc9c Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 02:42:19 -0700 Subject: [PATCH 45/86] Wrote classmethod from_dict helper function in app models goal.py module. --- app/models/goal.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/goal.py b/app/models/goal.py index 509060885..a386efa8e 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -11,3 +11,8 @@ def to_dict(self): id=self.goal_id, title=self.title ) + +@classmethod +def from_dict(cls, goal_data): + new_goal = Goal(title=goal_data["title"]) + return new_goal \ No newline at end of file From 1ffd7fa5aaa2cc520808b107c344803dd7e6646d Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 02:52:01 -0700 Subject: [PATCH 46/86] Renamed routes.py module in app folder to task_routes.py. --- app/task_routes.py | 114 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 app/task_routes.py diff --git a/app/task_routes.py b/app/task_routes.py new file mode 100644 index 000000000..db11a3fae --- /dev/null +++ b/app/task_routes.py @@ -0,0 +1,114 @@ +from app import db +from app.models.task import Task +from flask import Blueprint, jsonify, make_response, request, abort +from sqlalchemy import asc, desc +from datetime import datetime +import os, requests + + +tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") + +def validate_model(task_id): + try: + task_id = int(task_id) + except: + abort(make_response({"message": f"Task {task_id} is invalid"}, 400)) + + task = Task.query.get(task_id) + + if not task: + abort(make_response({"message": f"Task {task_id} not found"}, 404)) + + return task + +def slack_bot_message(message): + slack_api_key = os.environ.get("SLACK_BOT_TOKEN") + slack_url = "https://slack.com/api/chat.postMessage" + header = {"Authorization": slack_api_key} + + slack_query_params = { + "channel": "task-notifications", + "text": message + } + print(slack_api_key) + requests.post(url=slack_url, data=slack_query_params, headers=header) + +# ----------------------------------- +# ROUTES +# ----------------------------------- + +@tasks_bp.route("", methods=["POST"]) +def create_task(): + request_body = request.get_json() + + if "title" not in request_body or "description" not in request_body: + return make_response({"details": "Invalid data"}, 400) + + new_task = Task.from_dict(request_body) + + db.session.add(new_task) + db.session.commit() + + return make_response({"task": new_task.to_dict()}, 201) + +@tasks_bp.route("", methods=["GET"]) +def read_all_tasks(): + sort_query = request.args.get("sort") + if sort_query: + if sort_query == "asc": + tasks = Task.query.order_by(Task.title.asc()).all() + elif sort_query == "desc": + tasks = Task.query.order_by(Task.title.desc()).all() + else: + tasks = Task.query.all() + + all_tasks = [task.to_dict() for task in tasks] + + return jsonify(all_tasks), 200 + +@tasks_bp.route("/", methods=["GET"]) +def read_one_task(task_id): + task = validate_model(task_id) + + return make_response({"task": task.to_dict()}, 200) + +@tasks_bp.route("/", methods=["PUT"]) +def update_task(task_id): + task = validate_model(task_id) + request_body = request.get_json() + + task.title = request_body["title"] + task.description = request_body["description"] + + db.session.commit() + + return make_response({"task": task.to_dict()}, 200) + +@tasks_bp.route("//mark_complete", methods=["PATCH"]) +def mark_task_complete(task_id): + task = validate_model(task_id) + + task.completed_at = datetime.utcnow() + db.session.commit() + + slack_bot_message(f"Someone just completed the task {task.title}") + + return make_response({"task": task.to_dict()}, 200) + +@tasks_bp.route("//mark_incomplete", methods=["PATCH"]) +def mark_task_incomplete(task_id): + task = validate_model(task_id) + + task.completed_at = None + db.session.commit() + + return make_response({"task": task.to_dict()}, 200) + +@tasks_bp.route("/", methods=["DELETE"]) +def delete_task(task_id): + task = validate_model(task_id) + + db.session.delete(task) + db.session.commit() + + return make_response({"details": f"Task {task.task_id} \"{task.title}\" successfully deleted"}), 200 \ No newline at end of file From 3b0c98bd4f2f8d6caf957da96671f09f83e86b54 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 02:54:01 -0700 Subject: [PATCH 47/86] Created new module in app folder named goal_routes.py. --- app/goal_routes.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/goal_routes.py diff --git a/app/goal_routes.py b/app/goal_routes.py new file mode 100644 index 000000000..e69de29bb From eb2ca32344326e55c5d039ee583486a7254413b9 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 02:55:58 -0700 Subject: [PATCH 48/86] Fixed pathway typos in registered task and goal blueprints within the app dunder init module. --- app/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index e36c9f37d..ef217d821 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,10 +30,10 @@ def create_app(test_config=None): migrate.init_app(app, db) # Register Blueprints here - from app.models.task import tasks_bp + from app.task_routes import tasks_bp app.register_blueprint(tasks_bp) - from app.models.goal import goals_bp + from app.goal_routes import goals_bp app.register_blueprint(goals_bp) return app From 378dad6546163a9232388bf4cce9ff01e793d0f4 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 03:04:27 -0700 Subject: [PATCH 49/86] Implemented all necessary imports in app goal_routes.py module. --- app/goal_routes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/goal_routes.py b/app/goal_routes.py index e69de29bb..85aa1928a 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -0,0 +1,5 @@ +from app import db +from app.models.goal import Goal +from app.models.task import Task +from flask import Blueprint, jsonify, make_reponse, request + From 011b73ea200f3b3aee57df7119721efe8a97a851 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 03:08:58 -0700 Subject: [PATCH 50/86] Created goals_bp local variable to hold Blueprint instance route in app goal_routes.py module. --- app/goal_routes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/goal_routes.py b/app/goal_routes.py index 85aa1928a..a857ea879 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -3,3 +3,5 @@ from app.models.task import Task from flask import Blueprint, jsonify, make_reponse, request +goals_bp = Blueprint("goal_bp", __name__, url_prefix="/goals") + From ec1e8cfdea347a5ea5ac155df7438964aaf8e515 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 03:22:44 -0700 Subject: [PATCH 51/86] Wrote POST endpoint and create_goal function in app goal_routes.py module. --- app/goal_routes.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/goal_routes.py b/app/goal_routes.py index a857ea879..20f7a0d9b 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -5,3 +5,16 @@ goals_bp = Blueprint("goal_bp", __name__, url_prefix="/goals") +@goals_bp.route("", methods=["POST"]) +def create_goal(): + request_body = request.get_json() + + if "title" not in request_body: + return make_reponse({"details": "Invalid data"}, 400) + + new_goal = Goal.from_dict(request_body) + + db.session.add(new_goal) + db.session.commit() + + return make_reponse({"goal": new_goal.todict()}, 201) \ No newline at end of file From b8d0921f7c3bb2983643301a82f27268e732717f Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 03:26:29 -0700 Subject: [PATCH 52/86] Corrected silly typo in make_response command throughout app goal_routes.py module. --- app/goal_routes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index 20f7a0d9b..ebd5e2c43 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -1,7 +1,7 @@ from app import db from app.models.goal import Goal from app.models.task import Task -from flask import Blueprint, jsonify, make_reponse, request +from flask import Blueprint, jsonify, make_response, request goals_bp = Blueprint("goal_bp", __name__, url_prefix="/goals") @@ -10,11 +10,11 @@ def create_goal(): request_body = request.get_json() if "title" not in request_body: - return make_reponse({"details": "Invalid data"}, 400) + return make_response({"details": "Invalid data"}, 400) new_goal = Goal.from_dict(request_body) db.session.add(new_goal) db.session.commit() - return make_reponse({"goal": new_goal.todict()}, 201) \ No newline at end of file + return make_response({"goal": new_goal.todict()}, 201) \ No newline at end of file From 463a3a83f1afda2b56b82630473c7f1ca5bd6824 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 03:30:22 -0700 Subject: [PATCH 53/86] Imported asc, desc functionality from sqlalchemy in app goal_routes.py module. --- app/goal_routes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index ebd5e2c43..6510e718c 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -2,6 +2,8 @@ from app.models.goal import Goal from app.models.task import Task from flask import Blueprint, jsonify, make_response, request +from sqlalchemy import asc, desc + goals_bp = Blueprint("goal_bp", __name__, url_prefix="/goals") @@ -17,4 +19,5 @@ def create_goal(): db.session.add(new_goal) db.session.commit() - return make_response({"goal": new_goal.todict()}, 201) \ No newline at end of file + return make_response({"goal": new_goal.todict()}, 201) + From 614f249c3a53fe6af9f4e76114d02bffac8e3635 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 03:32:43 -0700 Subject: [PATCH 54/86] Wrote GET endpoint for read_all_goals function in app goal_routes.py module. --- app/goal_routes.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/goal_routes.py b/app/goal_routes.py index 6510e718c..3d1f9e9a3 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -21,3 +21,17 @@ def create_goal(): return make_response({"goal": new_goal.todict()}, 201) +@goals_bp.route("", methods=["GET"]) +def read_all_goals(): + sort_query = request.args.get("sort") + if sort_query: + if sort_query == "asc": + goals = Goal.query.order_by(Goal.title.asc()).all() + elif sort_query == "desc": + goals = Goal.query.order_by(Goal.title.desc()).all() + else: + goals = Goal.query.all() + + all_goals = [goal.to_dict() for goal in goals] + + return jsonify(all_goals), 200 \ No newline at end of file From b5c1aea9649c470e0d6273e8e5448025b4181d82 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 03:41:19 -0700 Subject: [PATCH 55/86] Removed validate_model and slack_bot_message functions from task_routes.py module and moved to new module named helper_functions.py in app folder. --- app/helper_functions.py | 24 ++++++++++++++++++++++++ app/task_routes.py | 28 ---------------------------- 2 files changed, 24 insertions(+), 28 deletions(-) create mode 100644 app/helper_functions.py diff --git a/app/helper_functions.py b/app/helper_functions.py new file mode 100644 index 000000000..cab5afdd0 --- /dev/null +++ b/app/helper_functions.py @@ -0,0 +1,24 @@ +def validate_model(task_id): + try: + task_id = int(task_id) + except: + abort(make_response({"message": f"Task {task_id} is invalid"}, 400)) + + task = Task.query.get(task_id) + + if not task: + abort(make_response({"message": f"Task {task_id} not found"}, 404)) + + return task + +def slack_bot_message(message): + slack_api_key = os.environ.get("SLACK_BOT_TOKEN") + slack_url = "https://slack.com/api/chat.postMessage" + header = {"Authorization": slack_api_key} + + slack_query_params = { + "channel": "task-notifications", + "text": message + } + print(slack_api_key) + requests.post(url=slack_url, data=slack_query_params, headers=header) \ No newline at end of file diff --git a/app/task_routes.py b/app/task_routes.py index db11a3fae..b1ad6a487 100644 --- a/app/task_routes.py +++ b/app/task_routes.py @@ -8,34 +8,6 @@ tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") -def validate_model(task_id): - try: - task_id = int(task_id) - except: - abort(make_response({"message": f"Task {task_id} is invalid"}, 400)) - - task = Task.query.get(task_id) - - if not task: - abort(make_response({"message": f"Task {task_id} not found"}, 404)) - - return task - -def slack_bot_message(message): - slack_api_key = os.environ.get("SLACK_BOT_TOKEN") - slack_url = "https://slack.com/api/chat.postMessage" - header = {"Authorization": slack_api_key} - - slack_query_params = { - "channel": "task-notifications", - "text": message - } - print(slack_api_key) - requests.post(url=slack_url, data=slack_query_params, headers=header) - -# ----------------------------------- -# ROUTES -# ----------------------------------- @tasks_bp.route("", methods=["POST"]) def create_task(): From 951ce91597169135901e40af5f8d9916bb756191 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 03:47:56 -0700 Subject: [PATCH 56/86] Removed routes.py module from app folder. --- app/routes.py | 114 -------------------------------------------------- 1 file changed, 114 deletions(-) delete mode 100644 app/routes.py diff --git a/app/routes.py b/app/routes.py deleted file mode 100644 index db11a3fae..000000000 --- a/app/routes.py +++ /dev/null @@ -1,114 +0,0 @@ -from app import db -from app.models.task import Task -from flask import Blueprint, jsonify, make_response, request, abort -from sqlalchemy import asc, desc -from datetime import datetime -import os, requests - - -tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") - -def validate_model(task_id): - try: - task_id = int(task_id) - except: - abort(make_response({"message": f"Task {task_id} is invalid"}, 400)) - - task = Task.query.get(task_id) - - if not task: - abort(make_response({"message": f"Task {task_id} not found"}, 404)) - - return task - -def slack_bot_message(message): - slack_api_key = os.environ.get("SLACK_BOT_TOKEN") - slack_url = "https://slack.com/api/chat.postMessage" - header = {"Authorization": slack_api_key} - - slack_query_params = { - "channel": "task-notifications", - "text": message - } - print(slack_api_key) - requests.post(url=slack_url, data=slack_query_params, headers=header) - -# ----------------------------------- -# ROUTES -# ----------------------------------- - -@tasks_bp.route("", methods=["POST"]) -def create_task(): - request_body = request.get_json() - - if "title" not in request_body or "description" not in request_body: - return make_response({"details": "Invalid data"}, 400) - - new_task = Task.from_dict(request_body) - - db.session.add(new_task) - db.session.commit() - - return make_response({"task": new_task.to_dict()}, 201) - -@tasks_bp.route("", methods=["GET"]) -def read_all_tasks(): - sort_query = request.args.get("sort") - if sort_query: - if sort_query == "asc": - tasks = Task.query.order_by(Task.title.asc()).all() - elif sort_query == "desc": - tasks = Task.query.order_by(Task.title.desc()).all() - else: - tasks = Task.query.all() - - all_tasks = [task.to_dict() for task in tasks] - - return jsonify(all_tasks), 200 - -@tasks_bp.route("/", methods=["GET"]) -def read_one_task(task_id): - task = validate_model(task_id) - - return make_response({"task": task.to_dict()}, 200) - -@tasks_bp.route("/", methods=["PUT"]) -def update_task(task_id): - task = validate_model(task_id) - request_body = request.get_json() - - task.title = request_body["title"] - task.description = request_body["description"] - - db.session.commit() - - return make_response({"task": task.to_dict()}, 200) - -@tasks_bp.route("//mark_complete", methods=["PATCH"]) -def mark_task_complete(task_id): - task = validate_model(task_id) - - task.completed_at = datetime.utcnow() - db.session.commit() - - slack_bot_message(f"Someone just completed the task {task.title}") - - return make_response({"task": task.to_dict()}, 200) - -@tasks_bp.route("//mark_incomplete", methods=["PATCH"]) -def mark_task_incomplete(task_id): - task = validate_model(task_id) - - task.completed_at = None - db.session.commit() - - return make_response({"task": task.to_dict()}, 200) - -@tasks_bp.route("/", methods=["DELETE"]) -def delete_task(task_id): - task = validate_model(task_id) - - db.session.delete(task) - db.session.commit() - - return make_response({"details": f"Task {task.task_id} \"{task.title}\" successfully deleted"}), 200 \ No newline at end of file From a2ab69138ccd2c85b4e0bd0ca6517395640eaf86 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 03:50:20 -0700 Subject: [PATCH 57/86] Imported helper_functions into task_routes.py and goal_routes.py modules. --- app/goal_routes.py | 9 ++++++++- app/task_routes.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index 3d1f9e9a3..2e8ff228b 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -1,6 +1,7 @@ from app import db from app.models.goal import Goal from app.models.task import Task +from app.helper_functions import validate_model, slack_bot_message from flask import Blueprint, jsonify, make_response, request from sqlalchemy import asc, desc @@ -34,4 +35,10 @@ def read_all_goals(): all_goals = [goal.to_dict() for goal in goals] - return jsonify(all_goals), 200 \ No newline at end of file + return jsonify(all_goals), 200 + +@goals_bp.route("/", methods=["GET"]) +def read_one_goal(goal_id): + goal = validate_model(goal_id) + + return make_response({"goal": goal.to_dict()}, 200) \ No newline at end of file diff --git a/app/task_routes.py b/app/task_routes.py index b1ad6a487..7329f4511 100644 --- a/app/task_routes.py +++ b/app/task_routes.py @@ -1,5 +1,6 @@ from app import db from app.models.task import Task +from app.helper_functions import validate_model, slack_bot_message from flask import Blueprint, jsonify, make_response, request, abort from sqlalchemy import asc, desc from datetime import datetime From 0a8d0c30deae0cc09ce9e547fd47e29586963adf Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 04:44:48 -0700 Subject: [PATCH 58/86] Moved all necessary imports from the task_routes.py module to the helper_functions.py module. --- app/helper_functions.py | 5 +++++ app/task_routes.py | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/helper_functions.py b/app/helper_functions.py index cab5afdd0..17a05a62d 100644 --- a/app/helper_functions.py +++ b/app/helper_functions.py @@ -1,3 +1,8 @@ +from app import db +from flask import make_response, abort +import os, requests + + def validate_model(task_id): try: task_id = int(task_id) diff --git a/app/task_routes.py b/app/task_routes.py index 7329f4511..e5b92a7ae 100644 --- a/app/task_routes.py +++ b/app/task_routes.py @@ -4,7 +4,6 @@ from flask import Blueprint, jsonify, make_response, request, abort from sqlalchemy import asc, desc from datetime import datetime -import os, requests tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") From f927bebb0911451be5ce361a501acb1ba840d631 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 04:48:25 -0700 Subject: [PATCH 59/86] Wrote PUT endpoint and update_goal function in app goal_routes.py module. --- app/goal_routes.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index 2e8ff228b..1d666e55b 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -1,6 +1,5 @@ from app import db from app.models.goal import Goal -from app.models.task import Task from app.helper_functions import validate_model, slack_bot_message from flask import Blueprint, jsonify, make_response, request from sqlalchemy import asc, desc @@ -41,4 +40,15 @@ def read_all_goals(): def read_one_goal(goal_id): goal = validate_model(goal_id) - return make_response({"goal": goal.to_dict()}, 200) \ No newline at end of file + return make_response({"goal": goal.to_dict()}, 200) + +@goals_bp.route("/", methods=["PUT"]) +def update_goal(goal_id): + goal = validate_model(goal_id) + request_body = request.get_json() + goal.title = request_body["title"] + + db.session.commit() + + return make_response({"goal": goal.to_dict()}, 200) + From 096f9ab1ba8f5f7f8670d4eb4c02a7bf69c6f44f Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 04:51:06 -0700 Subject: [PATCH 60/86] Wrote DELETE endpoint and delete_goal funciton in the app goal_routes.py module. --- app/goal_routes.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/goal_routes.py b/app/goal_routes.py index 1d666e55b..a5c1e1012 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -52,3 +52,11 @@ def update_goal(goal_id): return make_response({"goal": goal.to_dict()}, 200) +@goals_bp.route("/", methods=["DELETE"]) +def delete_goal(goal_id): + goal = validate_model(goal_id) + + db.session.delete(goal) + db.session.commit() + + return make_response({"details":f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"}), 200 \ No newline at end of file From a34eab234d82ab2a6689a637592f38f3c0a3a224 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 05:35:54 -0700 Subject: [PATCH 61/86] Refactored validate_model to accomodate any model in helper_functions.py. --- app/helper_functions.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/helper_functions.py b/app/helper_functions.py index 17a05a62d..62fb02617 100644 --- a/app/helper_functions.py +++ b/app/helper_functions.py @@ -3,18 +3,18 @@ import os, requests -def validate_model(task_id): +def validate_model(model_id): try: - task_id = int(task_id) + model_id = int(model_id) except: - abort(make_response({"message": f"Task {task_id} is invalid"}, 400)) + abort(make_response({"message": f"{cls.__name__} {model_id} is invalid"}, 400)) - task = Task.query.get(task_id) + model = cls.query.get(model_id) - if not task: - abort(make_response({"message": f"Task {task_id} not found"}, 404)) + if not model: + abort(make_response({"message": f"{cls.__name__} {model_id} not found"}, 404)) - return task + return model def slack_bot_message(message): slack_api_key = os.environ.get("SLACK_BOT_TOKEN") From e40414927bf2888de10c4bfda4073c35a92be4d7 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 05:42:57 -0700 Subject: [PATCH 62/86] Add cls parameters to validate_model helper funciton and all instances in which that function was called within the task_routes.py module. --- app/helper_functions.py | 2 +- app/task_routes.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/helper_functions.py b/app/helper_functions.py index 62fb02617..70b2d2818 100644 --- a/app/helper_functions.py +++ b/app/helper_functions.py @@ -3,7 +3,7 @@ import os, requests -def validate_model(model_id): +def validate_model(cls, model_id): try: model_id = int(model_id) except: diff --git a/app/task_routes.py b/app/task_routes.py index e5b92a7ae..283fc7f49 100644 --- a/app/task_routes.py +++ b/app/task_routes.py @@ -40,13 +40,13 @@ def read_all_tasks(): @tasks_bp.route("/", methods=["GET"]) def read_one_task(task_id): - task = validate_model(task_id) + task = validate_model(Task, task_id) return make_response({"task": task.to_dict()}, 200) @tasks_bp.route("/", methods=["PUT"]) def update_task(task_id): - task = validate_model(task_id) + task = validate_model(Task, task_id) request_body = request.get_json() task.title = request_body["title"] @@ -58,7 +58,7 @@ def update_task(task_id): @tasks_bp.route("//mark_complete", methods=["PATCH"]) def mark_task_complete(task_id): - task = validate_model(task_id) + task = validate_model(Task, task_id) task.completed_at = datetime.utcnow() db.session.commit() @@ -69,7 +69,7 @@ def mark_task_complete(task_id): @tasks_bp.route("//mark_incomplete", methods=["PATCH"]) def mark_task_incomplete(task_id): - task = validate_model(task_id) + task = validate_model(Task, task_id) task.completed_at = None db.session.commit() @@ -78,7 +78,7 @@ def mark_task_incomplete(task_id): @tasks_bp.route("/", methods=["DELETE"]) def delete_task(task_id): - task = validate_model(task_id) + task = validate_model(Task, task_id) db.session.delete(task) db.session.commit() From af29edb09158b3aa72dd0e92f6828d23b8efaaa6 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 05:58:41 -0700 Subject: [PATCH 63/86] Added cls (Goal) parameters to all instances in which the validate_model helper funciton is called within the goal_routes.py module. --- app/goal_routes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index a5c1e1012..9b07dcd4b 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -19,7 +19,7 @@ def create_goal(): db.session.add(new_goal) db.session.commit() - return make_response({"goal": new_goal.todict()}, 201) + return make_response({"goal": new_goal.to_dict()}, 201) @goals_bp.route("", methods=["GET"]) def read_all_goals(): @@ -38,13 +38,13 @@ def read_all_goals(): @goals_bp.route("/", methods=["GET"]) def read_one_goal(goal_id): - goal = validate_model(goal_id) + goal = validate_model(Goal, goal_id) return make_response({"goal": goal.to_dict()}, 200) @goals_bp.route("/", methods=["PUT"]) def update_goal(goal_id): - goal = validate_model(goal_id) + goal = validate_model(Goal, goal_id) request_body = request.get_json() goal.title = request_body["title"] @@ -54,7 +54,7 @@ def update_goal(goal_id): @goals_bp.route("/", methods=["DELETE"]) def delete_goal(goal_id): - goal = validate_model(goal_id) + goal = validate_model(Goal, goal_id) db.session.delete(goal) db.session.commit() From 4491c0742d117bd026b438bf12886ced91f68aea Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 06:01:27 -0700 Subject: [PATCH 64/86] Removed unnecessary app (implied) pathway under registered blueprints within app dunder init module. --- app/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index ef217d821..4d510c552 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,10 +30,10 @@ def create_app(test_config=None): migrate.init_app(app, db) # Register Blueprints here - from app.task_routes import tasks_bp + from .task_routes import tasks_bp app.register_blueprint(tasks_bp) - from app.goal_routes import goals_bp + from .goal_routes import goals_bp app.register_blueprint(goals_bp) return app From b328ed839bc26aed46f0d0adac135c80a4c3f0f8 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 06:09:47 -0700 Subject: [PATCH 65/86] Fixed indentation in app model goal.py module. --- app/models/goal.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/models/goal.py b/app/models/goal.py index a386efa8e..e0ecd1961 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -6,13 +6,13 @@ class Goal(db.Model): title = db.Column(db.String) tasks = db.relationship("Task", back_populates="goal", lazy=True) -def to_dict(self): - return dict( - id=self.goal_id, - title=self.title - ) + def to_dict(self): + return dict( + id=self.goal_id, + title=self.title + ) -@classmethod -def from_dict(cls, goal_data): - new_goal = Goal(title=goal_data["title"]) - return new_goal \ No newline at end of file + @classmethod + def from_dict(cls, goal_data): + new_goal = Goal(title=goal_data["title"]) + return new_goal \ No newline at end of file From 3a8b9b31478deea45a63e288da35e9e0434dd713 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 06:11:31 -0700 Subject: [PATCH 66/86] Removed slack_bot_message from imports in goal_routes.py module. --- app/goal_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index 9b07dcd4b..09ff2fa6e 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -1,6 +1,6 @@ from app import db from app.models.goal import Goal -from app.helper_functions import validate_model, slack_bot_message +from app.helper_functions import validate_model from flask import Blueprint, jsonify, make_response, request from sqlalchemy import asc, desc From 525863f86e7ef092bbd79d6c838c2d91463c0cfd Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 06:14:52 -0700 Subject: [PATCH 67/86] Corrected typo in pathway route for GET (read_one_goal) endpoint. --- app/goal_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index 09ff2fa6e..c6c2f2411 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -36,7 +36,7 @@ def read_all_goals(): return jsonify(all_goals), 200 -@goals_bp.route("/", methods=["GET"]) +@goals_bp.route("/", methods=["GET"]) def read_one_goal(goal_id): goal = validate_model(Goal, goal_id) From 5d6068ec32e6ab3214b369932f1cd44e13070c09 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 06:20:56 -0700 Subject: [PATCH 68/86] Completed necessary assertions for the test_get_goal_not_found within the test_wave_05.py module. --- tests/test_wave_05.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index aee7c52a1..9b807b370 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -1,7 +1,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_goals_no_saved_goals(client): # Act response = client.get("/goals") @@ -12,7 +12,7 @@ def test_get_goals_no_saved_goals(client): assert response_body == [] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_goals_one_saved_goal(client, one_goal): # Act response = client.get("/goals") @@ -29,7 +29,7 @@ def test_get_goals_one_saved_goal(client, one_goal): ] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_goal(client, one_goal): # Act response = client.get("/goals/1") @@ -46,18 +46,20 @@ def test_get_goal(client, one_goal): } -@pytest.mark.skip(reason="test to be completed by student") +#@pytest.mark.skip(reason="test to be completed by student") def test_get_goal_not_found(client): - pass + # Act response = client.get("/goals/1") response_body = response.get_json() - raise Exception("Complete test") + #raise Exception("Complete test") # Assert # ---- Complete Test ---- # assertion 1 goes here + assert response.status_code == 404 # assertion 2 goes here + assert response_body == {f"message": "Goal 1 not found"} # ---- Complete Test ---- From e0a3f39d03d106b3d3946fb25d8858d6e00f550b Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Thu, 11 May 2023 06:27:33 -0700 Subject: [PATCH 69/86] Completed act and assertion statements for test_update_goal function in the test_wave_05.py module. --- tests/test_wave_05.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index 9b807b370..2b2cf87ff 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -63,7 +63,7 @@ def test_get_goal_not_found(client): # ---- Complete Test ---- -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_create_goal(client): # Act response = client.post("/goals", json={ @@ -82,17 +82,28 @@ def test_create_goal(client): } -@pytest.mark.skip(reason="test to be completed by student") +#@pytest.mark.skip(reason="test to be completed by student") def test_update_goal(client, one_goal): - raise Exception("Complete test") + #raise Exception("Complete test") + # Act # ---- Complete Act Here ---- + response = client.put("/goals/1", json={"title": "Updated Goal Title"}) + response_body = response.get_json() # Assert # ---- Complete Assertions Here ---- # assertion 1 goes here + assert response.status_code == 200 # assertion 2 goes here + assert "goal" in response_body # assertion 3 goes here + assert response_body == { + "goal": { + "id": 1, + "title": "Updated Goal Title" + } + } # ---- Complete Assertions Here ---- From 8141fb7f2ad7a532a30061fc03b08f6b9641d82d Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Fri, 12 May 2023 19:45:53 -0700 Subject: [PATCH 70/86] Completed Act and Assert portion for test_update_goal_not_found function in test_wave_05.py module. --- tests/test_wave_05.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index 2b2cf87ff..1ccf30fbc 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -107,16 +107,21 @@ def test_update_goal(client, one_goal): # ---- Complete Assertions Here ---- -@pytest.mark.skip(reason="test to be completed by student") +#@pytest.mark.skip(reason="test to be completed by student") def test_update_goal_not_found(client): - raise Exception("Complete test") + #raise Exception("Complete test") + # Act # ---- Complete Act Here ---- + response = client.put("/goals/1", json={"title": "Updated Goal Title"}) + response_body = response.get_json() # Assert # ---- Complete Assertions Here ---- # assertion 1 goes here + assert response.status_code == 404 # assertion 2 goes here + assert response_body == {f"message": "Goal 1 not found"} # ---- Complete Assertions Here ---- From 2521be960f42cad9eaa8db17f98b9f86fab83936 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Fri, 12 May 2023 19:51:05 -0700 Subject: [PATCH 71/86] Completed assertion for test_delete_goal function in test_wave_05.py module. --- tests/test_wave_05.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index 1ccf30fbc..15cff2358 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -109,7 +109,7 @@ def test_update_goal(client, one_goal): #@pytest.mark.skip(reason="test to be completed by student") def test_update_goal_not_found(client): - #raise Exception("Complete test") + # raise Exception("Complete test") # Act # ---- Complete Act Here ---- @@ -125,7 +125,7 @@ def test_update_goal_not_found(client): # ---- Complete Assertions Here ---- -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_delete_goal(client, one_goal): # Act response = client.delete("/goals/1") @@ -141,8 +141,11 @@ def test_delete_goal(client, one_goal): # Check that the goal was deleted response = client.get("/goals/1") assert response.status_code == 404 + assert response_body == { + "details": 'Goal 1 "Build a habit of going outside daily" successfully deleted' + } - raise Exception("Complete test with assertion about response body") + # raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** From ebffe074f68ad2f6d60dd7ea8070f8b9a22fd561 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Fri, 12 May 2023 19:54:34 -0700 Subject: [PATCH 72/86] Completed Act and Assert portions of test_delete_goal_not_found funciton in test_wave_05.py module. --- tests/test_wave_05.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index 15cff2358..d8a01ed06 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -151,17 +151,21 @@ def test_delete_goal(client, one_goal): # ***************************************************************** -@pytest.mark.skip(reason="test to be completed by student") +#@pytest.mark.skip(reason="test to be completed by student") def test_delete_goal_not_found(client): - raise Exception("Complete test") + # raise Exception("Complete test") # Act # ---- Complete Act Here ---- + response = client.delete("/goals/1") + response_body = response.get_json() # Assert # ---- Complete Assertions Here ---- # assertion 1 goes here + assert response.status_code == 404 # assertion 2 goes here + assert response_body == {f"message": "Goal 1 not found"} # ---- Complete Assertions Here ---- From a158945ac6237d7fa4119461475cbca2bd2c5164 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 02:22:23 -0700 Subject: [PATCH 73/86] Imported Task model/class in goal_routes.py module. --- app/goal_routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/goal_routes.py b/app/goal_routes.py index c6c2f2411..83001bd6e 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -1,5 +1,6 @@ from app import db from app.models.goal import Goal +from app.models.task import Task from app.helper_functions import validate_model from flask import Blueprint, jsonify, make_response, request from sqlalchemy import asc, desc From fa30d3965fac891fc70853d3989c0fd5ca40918f Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 02:36:45 -0700 Subject: [PATCH 74/86] Wrote POST endpoint and create_tasks_under_goal function in goal_routes.py module. --- app/goal_routes.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/goal_routes.py b/app/goal_routes.py index 83001bd6e..8b41f11ec 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -22,6 +22,21 @@ def create_goal(): return make_response({"goal": new_goal.to_dict()}, 201) +@goals_bp.route("//tasks", methods=["POST"]) +def create_tasks_under_goal(goal_id): + request_body = request.get_json() + goal = validate_model(Goal, goal_id) + + task_list = [] + for task_id in request_body["task_ids"]: + task = validate_model(Task, task_id) + task.goal = goal + task_list.append(task_id) + + db.session.commit() + + return make_response({"id": goal.goal_id, "task_ids": task_list}, 200) + @goals_bp.route("", methods=["GET"]) def read_all_goals(): sort_query = request.args.get("sort") From 3f56a5c46fb620554d6b45347a0015067971b773 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 02:41:31 -0700 Subject: [PATCH 75/86] Wrote GET endpoint and read_tasks_under_goal function in goal_routes.py module. --- app/goal_routes.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/goal_routes.py b/app/goal_routes.py index 8b41f11ec..00373fa50 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -58,6 +58,16 @@ def read_one_goal(goal_id): return make_response({"goal": goal.to_dict()}, 200) +@goals_bp.route("//tasks", methods=["GET"]) +def read_tasks_under_goal(goal_id): + goal = validate_model(Goal, goal_id) + + task_list = [] + for task in goal.tasks: + task_list.append(task.to_dict()) + + return make_response({"id": goal.goal_id, "title": goal.title, "tasks": task_list}) + @goals_bp.route("/", methods=["PUT"]) def update_goal(goal_id): goal = validate_model(Goal, goal_id) From 6b9a1af2d68870877576b64c548394fc614f2f6c Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 02:45:32 -0700 Subject: [PATCH 76/86] Refactored read_tasks_under_goal function in goal_routes.py module. --- app/goal_routes.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index 00373fa50..6a4b93b53 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -61,12 +61,10 @@ def read_one_goal(goal_id): @goals_bp.route("//tasks", methods=["GET"]) def read_tasks_under_goal(goal_id): goal = validate_model(Goal, goal_id) - - task_list = [] - for task in goal.tasks: - task_list.append(task.to_dict()) + + goal_task_list = [task.to_dict() for task in goal.tasks] - return make_response({"id": goal.goal_id, "title": goal.title, "tasks": task_list}) + return make_response({"id": goal.goal_id, "title": goal.title, "tasks": goal_task_list}) @goals_bp.route("/", methods=["PUT"]) def update_goal(goal_id): From f7d2a081e537b980077993bd93b7432ddb680f40 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 02:50:43 -0700 Subject: [PATCH 77/86] Updated function names for better clarification in goal_routes.py module. --- app/goal_routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index 6a4b93b53..dadba4de9 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -23,7 +23,7 @@ def create_goal(): return make_response({"goal": new_goal.to_dict()}, 201) @goals_bp.route("//tasks", methods=["POST"]) -def create_tasks_under_goal(goal_id): +def create_tasks_under_one_goal(goal_id): request_body = request.get_json() goal = validate_model(Goal, goal_id) @@ -59,7 +59,7 @@ def read_one_goal(goal_id): return make_response({"goal": goal.to_dict()}, 200) @goals_bp.route("//tasks", methods=["GET"]) -def read_tasks_under_goal(goal_id): +def read_tasks_under_one_goal(goal_id): goal = validate_model(Goal, goal_id) goal_task_list = [task.to_dict() for task in goal.tasks] From ab28522a2adda02f1803274611cd0d7adf1f6ec4 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 02:53:46 -0700 Subject: [PATCH 78/86] Added status code to returned response body in read_tasks_under_one_goal function in goal_routes.py module. --- app/goal_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index dadba4de9..402e8cdeb 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -64,7 +64,7 @@ def read_tasks_under_one_goal(goal_id): goal_task_list = [task.to_dict() for task in goal.tasks] - return make_response({"id": goal.goal_id, "title": goal.title, "tasks": goal_task_list}) + return make_response({"id": goal.goal_id, "title": goal.title, "tasks": goal_task_list}, 200) @goals_bp.route("/", methods=["PUT"]) def update_goal(goal_id): From 2b6f24f58a327f610666c82b3db4803e7bc25762 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 02:58:45 -0700 Subject: [PATCH 79/86] Updated to_dict helper function to account for goal_id in task.py module. --- app/models/task.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/task.py b/app/models/task.py index c03924d5c..8394d7ca5 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -19,9 +19,10 @@ def to_dict(self): is_complete=True ) - if not self.completed_at: + if self.goal_id and not self.completed_at: return dict( id=self.task_id, + goal_id=self.goal_id, title=self.title, description=self.description, is_complete=False From 0d263cec7f8466c2b5d17345d94d28ce9b806937 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 03:04:00 -0700 Subject: [PATCH 80/86] Completed assertion for test_get_tasks_for_specific_goal_no_goal function in test_wave_06.py module. --- tests/test_wave_06.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index 8afa4325e..645068e72 100644 --- a/tests/test_wave_06.py +++ b/tests/test_wave_06.py @@ -2,7 +2,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_post_task_ids_to_goal(client, one_goal, three_tasks): # Act response = client.post("/goals/1/tasks", json={ @@ -23,7 +23,7 @@ def test_post_task_ids_to_goal(client, one_goal, three_tasks): assert len(Goal.query.get(1).tasks) == 3 -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_post_task_ids_to_goal_already_with_goals(client, one_task_belongs_to_one_goal, three_tasks): # Act response = client.post("/goals/1/tasks", json={ @@ -42,7 +42,7 @@ def test_post_task_ids_to_goal_already_with_goals(client, one_task_belongs_to_on assert len(Goal.query.get(1).tasks) == 2 -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_for_specific_goal_no_goal(client): # Act response = client.get("/goals/1/tasks") @@ -50,8 +50,9 @@ def test_get_tasks_for_specific_goal_no_goal(client): # Assert assert response.status_code == 404 + assert response_body == {"message": "Goal 1 not found"} - raise Exception("Complete test with assertion about response body") + # raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** From 494b3642582115d54cf08ebcdb8e49b970e05361 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 03:14:22 -0700 Subject: [PATCH 81/86] Ensured commited version of test_wave modules all have @pytest.mark.skip decorator commented out. --- tests/test_wave_01.py | 18 +++++++++--------- tests/test_wave_02.py | 4 ++-- tests/test_wave_03.py | 2 +- tests/test_wave_05.py | 2 +- tests/test_wave_06.py | 7 ++++--- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index c5d0c9dfd..c4b8656e6 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -32,7 +32,7 @@ def test_get_tasks_one_saved_tasks(client, one_task): ] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_task(client, one_task): # Act response = client.get("/tasks/1") @@ -51,7 +51,7 @@ def test_get_task(client, one_task): } -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_task_not_found(client): # Act response = client.get("/tasks/1") @@ -67,7 +67,7 @@ def test_get_task_not_found(client): # ***************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_create_task(client): # Act response = client.post("/tasks", json={ @@ -94,7 +94,7 @@ def test_create_task(client): assert new_task.completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_update_task(client, one_task): # Act response = client.put("/tasks/1", json={ @@ -120,7 +120,7 @@ def test_update_task(client, one_task): assert task.completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_update_task_not_found(client): # Act response = client.put("/tasks/1", json={ @@ -139,7 +139,7 @@ def test_update_task_not_found(client): # ***************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_delete_task(client, one_task): # Act response = client.delete("/tasks/1") @@ -154,7 +154,7 @@ def test_delete_task(client, one_task): assert Task.query.get(1) == None -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_delete_task_not_found(client): # Act response = client.delete("/tasks/1") @@ -172,7 +172,7 @@ def test_delete_task_not_found(client): assert Task.query.all() == [] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_create_task_must_contain_title(client): # Act response = client.post("/tasks", json={ @@ -189,7 +189,7 @@ def test_create_task_must_contain_title(client): assert Task.query.all() == [] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_create_task_must_contain_description(client): # Act response = client.post("/tasks", json={ diff --git a/tests/test_wave_02.py b/tests/test_wave_02.py index a087e0909..c9a76e6b1 100644 --- a/tests/test_wave_02.py +++ b/tests/test_wave_02.py @@ -1,7 +1,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_sorted_asc(client, three_tasks): # Act response = client.get("/tasks?sort=asc") @@ -29,7 +29,7 @@ def test_get_tasks_sorted_asc(client, three_tasks): ] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_sorted_desc(client, three_tasks): # Act response = client.get("/tasks?sort=desc") diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index 6299b2668..bf3591da9 100644 --- a/tests/test_wave_03.py +++ b/tests/test_wave_03.py @@ -135,7 +135,7 @@ def test_mark_complete_missing_task(client): # ***************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_mark_incomplete_missing_task(client): # Act response = client.patch("/tasks/1/mark_incomplete") diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index d8a01ed06..1e898f8d3 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -169,7 +169,7 @@ def test_delete_goal_not_found(client): # ---- Complete Assertions Here ---- -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_create_goal_missing_title(client): # Act response = client.post("/goals", json={}) diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index 645068e72..2f7d3b051 100644 --- a/tests/test_wave_06.py +++ b/tests/test_wave_06.py @@ -58,7 +58,7 @@ def test_get_tasks_for_specific_goal_no_goal(client): # ***************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_for_specific_goal_no_tasks(client, one_goal): # Act response = client.get("/goals/1/tasks") @@ -75,7 +75,7 @@ def test_get_tasks_for_specific_goal_no_tasks(client, one_goal): } -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_for_specific_goal(client, one_task_belongs_to_one_goal): # Act response = client.get("/goals/1/tasks") @@ -100,7 +100,8 @@ def test_get_tasks_for_specific_goal(client, one_task_belongs_to_one_goal): } -@pytest.mark.skip(reason="No way to test this feature yet") +# +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_task_includes_goal_id(client, one_task_belongs_to_one_goal): response = client.get("/tasks/1") response_body = response.get_json() From c2a4485efb356567d62e712889bb400c2e42b39d Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 03:16:07 -0700 Subject: [PATCH 82/86] Added all migrations to project repo. --- ..._added_goal_id_attribute_to_connect_to_.py | 30 +++++++++++++++ .../versions/b01eaf1ddd1f_adds_goal_model.py | 28 ++++++++++++++ migrations/versions/dfe8847b101b_.py | 38 +++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 migrations/versions/1a239286f5b7_added_goal_id_attribute_to_connect_to_.py create mode 100644 migrations/versions/b01eaf1ddd1f_adds_goal_model.py create mode 100644 migrations/versions/dfe8847b101b_.py diff --git a/migrations/versions/1a239286f5b7_added_goal_id_attribute_to_connect_to_.py b/migrations/versions/1a239286f5b7_added_goal_id_attribute_to_connect_to_.py new file mode 100644 index 000000000..80026f88f --- /dev/null +++ b/migrations/versions/1a239286f5b7_added_goal_id_attribute_to_connect_to_.py @@ -0,0 +1,30 @@ +"""Added goal_id attribute to connect to goal table + +Revision ID: 1a239286f5b7 +Revises: b01eaf1ddd1f +Create Date: 2023-05-11 03:26:53.040344 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '1a239286f5b7' +down_revision = 'b01eaf1ddd1f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('goal_id', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'task', 'goal', ['goal_id'], ['goal_id']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'task', type_='foreignkey') + op.drop_column('task', 'goal_id') + # ### end Alembic commands ### diff --git a/migrations/versions/b01eaf1ddd1f_adds_goal_model.py b/migrations/versions/b01eaf1ddd1f_adds_goal_model.py new file mode 100644 index 000000000..dfaeca3c9 --- /dev/null +++ b/migrations/versions/b01eaf1ddd1f_adds_goal_model.py @@ -0,0 +1,28 @@ +"""adds Goal model + +Revision ID: b01eaf1ddd1f +Revises: dfe8847b101b +Create Date: 2023-05-11 02:19:08.817278 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b01eaf1ddd1f' +down_revision = 'dfe8847b101b' +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/dfe8847b101b_.py b/migrations/versions/dfe8847b101b_.py new file mode 100644 index 000000000..55f705911 --- /dev/null +++ b/migrations/versions/dfe8847b101b_.py @@ -0,0 +1,38 @@ +"""Created new task table because could not reset task_id to 0 in original task table. + +Revision ID: dfe8847b101b +Revises: f1bf59b1ec0f +Create Date: 2023-05-08 03:51:57.762248 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'dfe8847b101b' +down_revision = 'f1bf59b1ec0f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('task', 'description', + existing_type=sa.TEXT(), + nullable=False) + op.alter_column('task', 'title', + existing_type=sa.TEXT(), + nullable=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('task', 'title', + existing_type=sa.TEXT(), + nullable=True) + op.alter_column('task', 'description', + existing_type=sa.TEXT(), + nullable=True) + # ### end Alembic commands ### From a5972f2c1bd20ab4868c530c75af2159c989f9c3 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 03:20:40 -0700 Subject: [PATCH 83/86] Updated spacing between functions for consistency in goal_routes.py and task_routes.py modules. --- app/goal_routes.py | 7 +++++++ app/task_routes.py | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/app/goal_routes.py b/app/goal_routes.py index 402e8cdeb..d72f3252c 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -8,6 +8,7 @@ goals_bp = Blueprint("goal_bp", __name__, url_prefix="/goals") + @goals_bp.route("", methods=["POST"]) def create_goal(): request_body = request.get_json() @@ -22,6 +23,7 @@ def create_goal(): return make_response({"goal": new_goal.to_dict()}, 201) + @goals_bp.route("//tasks", methods=["POST"]) def create_tasks_under_one_goal(goal_id): request_body = request.get_json() @@ -37,6 +39,7 @@ def create_tasks_under_one_goal(goal_id): return make_response({"id": goal.goal_id, "task_ids": task_list}, 200) + @goals_bp.route("", methods=["GET"]) def read_all_goals(): sort_query = request.args.get("sort") @@ -52,12 +55,14 @@ def read_all_goals(): return jsonify(all_goals), 200 + @goals_bp.route("/", methods=["GET"]) def read_one_goal(goal_id): goal = validate_model(Goal, goal_id) return make_response({"goal": goal.to_dict()}, 200) + @goals_bp.route("//tasks", methods=["GET"]) def read_tasks_under_one_goal(goal_id): goal = validate_model(Goal, goal_id) @@ -66,6 +71,7 @@ def read_tasks_under_one_goal(goal_id): return make_response({"id": goal.goal_id, "title": goal.title, "tasks": goal_task_list}, 200) + @goals_bp.route("/", methods=["PUT"]) def update_goal(goal_id): goal = validate_model(Goal, goal_id) @@ -76,6 +82,7 @@ def update_goal(goal_id): return make_response({"goal": goal.to_dict()}, 200) + @goals_bp.route("/", methods=["DELETE"]) def delete_goal(goal_id): goal = validate_model(Goal, goal_id) diff --git a/app/task_routes.py b/app/task_routes.py index 283fc7f49..cdda145fc 100644 --- a/app/task_routes.py +++ b/app/task_routes.py @@ -23,6 +23,7 @@ def create_task(): return make_response({"task": new_task.to_dict()}, 201) + @tasks_bp.route("", methods=["GET"]) def read_all_tasks(): sort_query = request.args.get("sort") @@ -38,12 +39,14 @@ def read_all_tasks(): return jsonify(all_tasks), 200 + @tasks_bp.route("/", methods=["GET"]) def read_one_task(task_id): task = validate_model(Task, task_id) return make_response({"task": task.to_dict()}, 200) + @tasks_bp.route("/", methods=["PUT"]) def update_task(task_id): task = validate_model(Task, task_id) @@ -56,6 +59,7 @@ def update_task(task_id): return make_response({"task": task.to_dict()}, 200) + @tasks_bp.route("//mark_complete", methods=["PATCH"]) def mark_task_complete(task_id): task = validate_model(Task, task_id) @@ -67,6 +71,7 @@ def mark_task_complete(task_id): return make_response({"task": task.to_dict()}, 200) + @tasks_bp.route("//mark_incomplete", methods=["PATCH"]) def mark_task_incomplete(task_id): task = validate_model(Task, task_id) @@ -76,6 +81,7 @@ def mark_task_incomplete(task_id): return make_response({"task": task.to_dict()}, 200) + @tasks_bp.route("/", methods=["DELETE"]) def delete_task(task_id): task = validate_model(Task, task_id) From c253929b46955b75e70fa53101fde5d5a511080e Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 03:55:36 -0700 Subject: [PATCH 84/86] Added ascii art to goal.py module. --- app/models/goal.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/models/goal.py b/app/models/goal.py index e0ecd1961..3f0d4097f 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -15,4 +15,17 @@ def to_dict(self): @classmethod def from_dict(cls, goal_data): new_goal = Goal(title=goal_data["title"]) - return new_goal \ No newline at end of file + return new_goal + + +# _______________ +# | | +# | Thank | +# | you for | +# | reviewing | +# | my code! | +# |_____________| +# || +# (\__/ || +# (•ㅅ•) || +# /   >|| \ No newline at end of file From c27a1bc84098f00b4f5cfb4a8f068e38a77d5226 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Sat, 13 May 2023 03:57:50 -0700 Subject: [PATCH 85/86] Fixed ascii bunny ear in goal.py module. --- app/models/goal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/goal.py b/app/models/goal.py index 3f0d4097f..ed1724b6a 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -26,6 +26,6 @@ def from_dict(cls, goal_data): # | my code! | # |_____________| # || -# (\__/ || +# (\__/)|| # (•ㅅ•) || # /   >|| \ No newline at end of file From 304e0253d2507492fe0926dd91bc1a0154d6f961 Mon Sep 17 00:00:00 2001 From: mlhuynh Date: Mon, 15 May 2023 23:48:40 -0700 Subject: [PATCH 86/86] Connect to Render db. --- app/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 4d510c552..b49383922 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -15,8 +15,8 @@ def create_app(test_config=None): app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False if test_config is None: - app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get( - "SQLALCHEMY_DATABASE_URI") + #app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get("SQLALCHEMY_DATABASE_URI") + app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("RENDER_DATABASE_URI") else: app.config["TESTING"] = True app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(