From c3591544134c6ce4187cc6733835a8c903083f67 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Fri, 6 May 2022 19:38:50 -0400 Subject: [PATCH 01/29] added Task attributes, registered Blueprint --- app/__init__.py | 2 + app/models/task.py | 3 + app/routes.py | 6 +- migrations/README | 1 + migrations/alembic.ini | 45 +++++++++ migrations/env.py | 96 +++++++++++++++++++ migrations/script.py.mako | 24 +++++ ..._changed_completed_at_nullable_to_false.py | 32 +++++++ .../7e2bad1312ad_added_task_attributes.py | 39 ++++++++ ...6_changed_completed_at_nullable_to_true.py | 32 +++++++ 10 files changed, 279 insertions(+), 1 deletion(-) 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/4e4125975412_changed_completed_at_nullable_to_false.py create mode 100644 migrations/versions/7e2bad1312ad_added_task_attributes.py create mode 100644 migrations/versions/a2ea7a98b436_changed_completed_at_nullable_to_true.py diff --git a/app/__init__.py b/app/__init__.py index 2764c4cc8..e253d096f 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 bp + app.register_blueprint(bp) return app diff --git a/app/models/task.py b/app/models/task.py index c91ab281f..d098f7ee2 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -3,3 +3,6 @@ class Task(db.Model): task_id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) + description = db.Column(db.String) + completed_at = db.Column(db.DateTime, nullable=True) diff --git a/app/routes.py b/app/routes.py index 3aae38d49..176bbbe52 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1 +1,5 @@ -from flask import Blueprint \ No newline at end of file +from app import db +from app.models.task import Task +from flask import Blueprint, jsonify, make_response, request + +bp = Blueprint("tasks", __name__, url_prefix="/tasks") \ No newline at end of file diff --git a/migrations/README b/migrations/README new file mode 100644 index 000000000..98e4f9c44 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 000000000..f8ed4801f --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,45 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 000000000..8b3fb3353 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,96 @@ +from __future__ import with_statement + +import logging +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option( + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 000000000..2c0156303 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/4e4125975412_changed_completed_at_nullable_to_false.py b/migrations/versions/4e4125975412_changed_completed_at_nullable_to_false.py new file mode 100644 index 000000000..03f59b338 --- /dev/null +++ b/migrations/versions/4e4125975412_changed_completed_at_nullable_to_false.py @@ -0,0 +1,32 @@ +"""changed completed_at nullable to False + +Revision ID: 4e4125975412 +Revises: 7e2bad1312ad +Create Date: 2022-05-06 16:02:55.050773 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '4e4125975412' +down_revision = '7e2bad1312ad' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('task', 'completed_at', + existing_type=postgresql.TIMESTAMP(), + nullable=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('task', 'completed_at', + existing_type=postgresql.TIMESTAMP(), + nullable=True) + # ### end Alembic commands ### diff --git a/migrations/versions/7e2bad1312ad_added_task_attributes.py b/migrations/versions/7e2bad1312ad_added_task_attributes.py new file mode 100644 index 000000000..5fb5e03fe --- /dev/null +++ b/migrations/versions/7e2bad1312ad_added_task_attributes.py @@ -0,0 +1,39 @@ +"""added Task attributes + +Revision ID: 7e2bad1312ad +Revises: +Create Date: 2022-05-06 13:52:57.119607 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7e2bad1312ad' +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(), nullable=False), + sa.Column('title', sa.String(), nullable=True), + sa.Column('description', sa.String(), nullable=True), + sa.Column('completed_at', sa.DateTime(), 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 ### diff --git a/migrations/versions/a2ea7a98b436_changed_completed_at_nullable_to_true.py b/migrations/versions/a2ea7a98b436_changed_completed_at_nullable_to_true.py new file mode 100644 index 000000000..4a16d69cb --- /dev/null +++ b/migrations/versions/a2ea7a98b436_changed_completed_at_nullable_to_true.py @@ -0,0 +1,32 @@ +"""changed completed_at nullable to True + +Revision ID: a2ea7a98b436 +Revises: 4e4125975412 +Create Date: 2022-05-06 16:26:23.241612 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'a2ea7a98b436' +down_revision = '4e4125975412' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('task', 'completed_at', + existing_type=postgresql.TIMESTAMP(), + nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('task', 'completed_at', + existing_type=postgresql.TIMESTAMP(), + nullable=False) + # ### end Alembic commands ### From da8b4b9dba4801f4f256e24398db39958b67f9c5 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Sat, 7 May 2022 12:52:09 -0400 Subject: [PATCH 02/29] started working on create_task POST route --- app/models/task.py | 18 ++++++++++++++++++ app/routes.py | 28 +++++++++++++++++++++++++--- tests/test_wave_01.py | 2 +- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index d098f7ee2..ee2a7de67 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -6,3 +6,21 @@ class Task(db.Model): title = db.Column(db.String) description = db.Column(db.String) completed_at = db.Column(db.DateTime, nullable=True) + + +def to_dict(self): + return { + "task_id": self.task_id, + "title": self.title, + "description": self.description, + "is_complete": self.completed_at + } + + +# @classmethod +# def from_dict(cls, data_dict): +# return cls( +# title=data_dict["title"], +# description=data_dict["description"], +# completed_at=data_dict["completed_at"] +# ) \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 176bbbe52..ae58d2d97 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,5 +1,27 @@ -from app import db -from app.models.task import Task from flask import Blueprint, jsonify, make_response, request +from app.models.task import Task +from app import db + + +bp = Blueprint("tasks", __name__, url_prefix="/tasks") + + +@bp.route("", methods=("POST",)) +def create_task(): + request_body = request.get_json() + + new_task = Task(title=request_body["title"], description=request_body["description"]) + + db.session.add(new_task) + db.session.commit() + + return make_response({ + "task": { + "title": new_task.title, + "description": new_task.description, + "id": new_task.task_id, + "is_complete": new_task.completed_at + } + }), 201 -bp = Blueprint("tasks", __name__, url_prefix="/tasks") \ No newline at end of file + # return make_response(Task.to_dict(new_task)) \ No newline at end of file diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index dca626d78..292d2a5c4 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -66,7 +66,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={ From 522cdb11d0107e558a475fe185130567f45d29cf Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Sat, 7 May 2022 14:15:50 -0400 Subject: [PATCH 03/29] fixed Task indentation --- app/models/task.py | 29 +++++++++++++---------------- app/routes.py | 11 +---------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index ee2a7de67..cea48f356 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -1,26 +1,23 @@ from app import db - class Task(db.Model): task_id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) description = db.Column(db.String) completed_at = db.Column(db.DateTime, nullable=True) - -def to_dict(self): - return { - "task_id": self.task_id, - "title": self.title, - "description": self.description, - "is_complete": self.completed_at - } + def to_dict(self): + return dict(task_id=self.task_id, + title=self.title, + description=self.description, + is_complete=self.completed_at + ) -# @classmethod -# def from_dict(cls, data_dict): -# return cls( -# title=data_dict["title"], -# description=data_dict["description"], -# completed_at=data_dict["completed_at"] -# ) \ No newline at end of file + @classmethod + def from_dict(cls, data_dict): + return cls( + dict(title=data_dict["title"], + description=data_dict["description"], + completed_at=data_dict["completed_at"]) + ) \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index ae58d2d97..1cbd3921c 100644 --- a/app/routes.py +++ b/app/routes.py @@ -15,13 +15,4 @@ def create_task(): db.session.add(new_task) db.session.commit() - return make_response({ - "task": { - "title": new_task.title, - "description": new_task.description, - "id": new_task.task_id, - "is_complete": new_task.completed_at - } - }), 201 - - # return make_response(Task.to_dict(new_task)) \ No newline at end of file + return make_response({"task": Task.to_dict(new_task)}, 201) \ No newline at end of file From f82647d1413d68099d023e669161023008bfc423 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Sat, 7 May 2022 14:46:30 -0400 Subject: [PATCH 04/29] passed create_task test --- app/models/task.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index cea48f356..a784fa650 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -6,18 +6,21 @@ class Task(db.Model): description = db.Column(db.String) completed_at = db.Column(db.DateTime, nullable=True) + def to_dict(self): - return dict(task_id=self.task_id, - title=self.title, - description=self.description, - is_complete=self.completed_at - ) + return dict( + id=self.task_id, + title=self.title, + description=self.description, + is_complete=False if self.completed_at == None else self.completed_at + ) @classmethod def from_dict(cls, data_dict): return cls( - dict(title=data_dict["title"], - description=data_dict["description"], - completed_at=data_dict["completed_at"]) + dict( + title=data_dict["title"], + description=data_dict["description"], + completed_at=data_dict["completed_at"]) ) \ No newline at end of file From ff50e45e8d95a8253ec800dea1de09caa5051296 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Sat, 7 May 2022 14:57:02 -0400 Subject: [PATCH 05/29] created GET route --- app/routes.py | 13 ++++++++++++- tests/test_wave_01.py | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/routes.py b/app/routes.py index 1cbd3921c..f67959fed 100644 --- a/app/routes.py +++ b/app/routes.py @@ -15,4 +15,15 @@ def create_task(): db.session.add(new_task) db.session.commit() - return make_response({"task": Task.to_dict(new_task)}, 201) \ No newline at end of file + return make_response({"task": Task.to_dict(new_task)}, 201) + + +@bp.route("", methods=("GET",)) +def read_tasks(): + """ + As a client, I want to be able to make a `GET` request to `/tasks` when there is at least one saved task and get this response: + + `200 OK` + """ + tasks = Task.query.all() + return jsonify([Task.to_dict(task) for task in tasks]) \ No newline at end of file diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index 292d2a5c4..c059f3244 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") From 355fbb55f792cbce96d4b63523433510b6cda5fb Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Mon, 9 May 2022 19:42:54 -0400 Subject: [PATCH 06/29] completed GET route --- app/models/task.py | 4 +++- app/routes.py | 43 ++++++++++++++++++++++++++++++++++--------- tests/test_wave_01.py | 12 ++++++++---- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index a784fa650..70777b41f 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -14,7 +14,9 @@ def to_dict(self): description=self.description, is_complete=False if self.completed_at == None else self.completed_at ) - + + def override_task(self, data_dict): + pass @classmethod def from_dict(cls, data_dict): diff --git a/app/routes.py b/app/routes.py index f67959fed..0625045d7 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,4 +1,4 @@ -from flask import Blueprint, jsonify, make_response, request +from flask import Blueprint, jsonify, make_response, request, abort from app.models.task import Task from app import db @@ -18,12 +18,37 @@ def create_task(): return make_response({"task": Task.to_dict(new_task)}, 201) -@bp.route("", methods=("GET",)) -def read_tasks(): - """ - As a client, I want to be able to make a `GET` request to `/tasks` when there is at least one saved task and get this response: +@bp.route("/", methods=("GET",)) +def read_tasks(task_id=""): + if not task_id: + tasks = Task.query.all() + return jsonify([Task.to_dict(task) for task in tasks]) - `200 OK` - """ - tasks = Task.query.all() - return jsonify([Task.to_dict(task) for task in tasks]) \ No newline at end of file + task = validate_task_id(task_id) + return make_response({"task": Task.to_dict(task)}, 200) + + +# @bp.route("/", methods=("PUT",)) +# def read_tasks(task_id=""): +# request_body = request.get_json() +# task = validate_task_id(task_id) + +# task.title = request_body["title"] +# task.description = request_body["description"] + +# db.session.commit() +# return jsonify({"task": Task.to_dict(task)}, 200) + + +def validate_task_id(task_id): + try: + task_id = int(task_id) + except: + abort(make_response({"message": f"Invalid task id {task_id}"}, 400)) + + task = Task.query.get(task_id) + + if not task: + abort(make_response({"message": f"Task id not found"}, 404)) + # abort(make_response({"message": f"Task id {task_id} not found"}, 404)) + return task \ No newline at end of file diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index c059f3244..00449ac90 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") @@ -60,10 +60,14 @@ def test_get_task_not_found(client): # Assert assert response.status_code == 404 - 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*************** # ***************************************************************** + # how to get task_id into response body? + # assert response_body == {"message": f"Task id {task_id} not found"} + assert response_body == {"message": f"Task id not found"} + # @pytest.mark.skip(reason="No way to test this feature yet") @@ -93,7 +97,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={ From 1c54fb702bad5aad4cb0d56f0ccba65cb8f61f70 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Mon, 9 May 2022 20:41:03 -0400 Subject: [PATCH 07/29] passed test_delete_task --- app/models/task.py | 20 ++++++++++---------- app/routes.py | 34 ++++++++++++++++++++-------------- tests/test_wave_01.py | 20 ++++++++++---------- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 70777b41f..a1b8b0a33 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -15,14 +15,14 @@ def to_dict(self): is_complete=False if self.completed_at == None else self.completed_at ) - def override_task(self, data_dict): - pass + # def override_task(self, data_dict): + # pass - @classmethod - def from_dict(cls, data_dict): - return cls( - dict( - title=data_dict["title"], - description=data_dict["description"], - completed_at=data_dict["completed_at"]) - ) \ No newline at end of file + # @classmethod + # def from_dict(cls, data_dict): + # return cls( + # dict( + # title=data_dict["title"], + # description=data_dict["description"], + # completed_at=data_dict["completed_at"]) + # ) \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 0625045d7..c9eb2aeb0 100644 --- a/app/routes.py +++ b/app/routes.py @@ -9,7 +9,6 @@ @bp.route("", methods=("POST",)) def create_task(): request_body = request.get_json() - new_task = Task(title=request_body["title"], description=request_body["description"]) db.session.add(new_task) @@ -19,36 +18,43 @@ def create_task(): @bp.route("/", methods=("GET",)) -def read_tasks(task_id=""): +def read_tasks(task_id): if not task_id: tasks = Task.query.all() return jsonify([Task.to_dict(task) for task in tasks]) task = validate_task_id(task_id) - return make_response({"task": Task.to_dict(task)}, 200) + return make_response(jsonify({"task": Task.to_dict(task)}, 200)) -# @bp.route("/", methods=("PUT",)) -# def read_tasks(task_id=""): -# request_body = request.get_json() -# task = validate_task_id(task_id) +@bp.route("/", methods=("PUT",)) +def update_task(task_id): + request_body = request.get_json() + task = validate_task_id(task_id) + + task.title = request_body["title"] + task.description = request_body["description"] + db.session.commit() -# task.title = request_body["title"] -# task.description = request_body["description"] + return make_response(jsonify({"task": Task.to_dict(task)}, 200)) -# db.session.commit() -# return jsonify({"task": Task.to_dict(task)}, 200) + +@bp.route("/", methods=("DELETE",)) +def delete_task(task_id): + task = validate_task_id(task_id) + db.session.delete(task) + db.session.commit() + return {"details": f"Task {task_id} \"{task.title}\" successfully deleted"}, 200 def validate_task_id(task_id): try: task_id = int(task_id) except: - abort(make_response({"message": f"Invalid task id {task_id}"}, 400)) + abort(make_response({"message": "Invalid task id"}, 400)) task = Task.query.get(task_id) if not task: - abort(make_response({"message": f"Task id not found"}, 404)) - # abort(make_response({"message": f"Task id {task_id} not found"}, 404)) + abort(make_response({"message": "Task id not found"}, 404)) return task \ No newline at end of file diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index 00449ac90..a13539cff 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") @@ -66,8 +66,7 @@ def test_get_task_not_found(client): # ***************************************************************** # how to get task_id into response body? # assert response_body == {"message": f"Task id {task_id} not found"} - assert response_body == {"message": f"Task id not found"} - + assert response_body == {"message": "Task id not found"} # @pytest.mark.skip(reason="No way to test this feature yet") @@ -123,7 +122,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={ @@ -135,13 +134,13 @@ def test_update_task_not_found(client): # Assert assert response.status_code == 404 - 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*************** # ***************************************************************** + assert response_body == {"message": "Task id not found"} - -@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") @@ -156,7 +155,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") @@ -165,12 +164,13 @@ def test_delete_task_not_found(client): # Assert assert response.status_code == 404 - 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*************** # ***************************************************************** assert Task.query.all() == [] + assert response_body == {"details": "Task"} @pytest.mark.skip(reason="No way to test this feature yet") From 807ad61dca3c287a83796bd7c8afc509f4c22c0c Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Tue, 10 May 2022 11:46:55 -0400 Subject: [PATCH 08/29] changed some returns for routes --- app/models/task.py | 5 +++-- app/routes.py | 14 +++++++------- tests/test_wave_01.py | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index a1b8b0a33..b1b9fde1f 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -15,8 +15,9 @@ def to_dict(self): is_complete=False if self.completed_at == None else self.completed_at ) - # def override_task(self, data_dict): - # pass + def override_task(self, data_dict): + self.title = data_dict["title"] + self.description = data_dict["description"] # @classmethod # def from_dict(cls, data_dict): diff --git a/app/routes.py b/app/routes.py index c9eb2aeb0..eff390ab6 100644 --- a/app/routes.py +++ b/app/routes.py @@ -9,12 +9,12 @@ @bp.route("", methods=("POST",)) def create_task(): request_body = request.get_json() - new_task = Task(title=request_body["title"], description=request_body["description"]) + task = Task(title=request_body["title"], description=request_body["description"]) - db.session.add(new_task) + db.session.add(task) db.session.commit() - return make_response({"task": Task.to_dict(new_task)}, 201) + return make_response({"task": Task.to_dict(task)}, 201) @bp.route("/", methods=("GET",)) @@ -24,7 +24,7 @@ def read_tasks(task_id): return jsonify([Task.to_dict(task) for task in tasks]) task = validate_task_id(task_id) - return make_response(jsonify({"task": Task.to_dict(task)}, 200)) + return make_response(jsonify({"task": task.to_dict()}), 200) @bp.route("/", methods=("PUT",)) @@ -32,11 +32,11 @@ def update_task(task_id): request_body = request.get_json() task = validate_task_id(task_id) - task.title = request_body["title"] - task.description = request_body["description"] + updated_task = Task.override_task(task, request_body) + db.session.add(updated_task) db.session.commit() - return make_response(jsonify({"task": Task.to_dict(task)}, 200)) + return make_response(jsonify({"task": task.to_dict()}, 200)) @bp.route("/", methods=("DELETE",)) diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index a13539cff..672713a97 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") From 533d1bd31523ee6a307845bb23d97148b43de0b3 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Tue, 10 May 2022 12:16:48 -0400 Subject: [PATCH 09/29] changed wave 1 assertions --- ada-project-docs/wave_01.md | 2 +- app/routes.py | 10 +++++----- tests/test_wave_01.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ada-project-docs/wave_01.md b/ada-project-docs/wave_01.md index b23485a67..a598f7f4c 100644 --- a/ada-project-docs/wave_01.md +++ b/ada-project-docs/wave_01.md @@ -136,7 +136,7 @@ As a client, I want to be able to make a `GET` request to `/tasks/1` when there As a client, I want to be able to make a `PUT` request to `/tasks/1` when there is at least one saved task with this request body: -```json +```jso { "title": "Updated Task Title", "description": "Updated Test Description", diff --git a/app/routes.py b/app/routes.py index eff390ab6..42427b0f1 100644 --- a/app/routes.py +++ b/app/routes.py @@ -14,14 +14,14 @@ def create_task(): db.session.add(task) db.session.commit() - return make_response({"task": Task.to_dict(task)}, 201) + return make_response(jsonify({"task": task.to_dict()}), 201) @bp.route("/", methods=("GET",)) def read_tasks(task_id): if not task_id: tasks = Task.query.all() - return jsonify([Task.to_dict(task) for task in tasks]) + return jsonify([task.to_dict() for task in tasks]) task = validate_task_id(task_id) return make_response(jsonify({"task": task.to_dict()}), 200) @@ -32,7 +32,7 @@ def update_task(task_id): request_body = request.get_json() task = validate_task_id(task_id) - updated_task = Task.override_task(task, request_body) + updated_task = task.override_task(request_body) db.session.add(updated_task) db.session.commit() @@ -51,10 +51,10 @@ def validate_task_id(task_id): try: task_id = int(task_id) except: - abort(make_response({"message": "Invalid task id"}, 400)) + abort(make_response({"message": "Invalid data"}, 400)) task = Task.query.get(task_id) if not task: - abort(make_response({"message": "Task id not found"}, 404)) + abort(make_response({"message": "Task 1 not found"}, 404)) return task \ No newline at end of file diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index 672713a97..aac507a27 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -66,7 +66,7 @@ def test_get_task_not_found(client): # ***************************************************************** # how to get task_id into response body? # assert response_body == {"message": f"Task id {task_id} not found"} - assert response_body == {"message": "Task id not found"} + assert response_body == {"message": "Task 1 not found"} # @pytest.mark.skip(reason="No way to test this feature yet") @@ -138,7 +138,7 @@ def test_update_task_not_found(client): # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** - assert response_body == {"message": "Task id not found"} + assert response_body == {"message": "Task 1 not found"} # @pytest.mark.skip(reason="No way to test this feature yet") def test_delete_task(client, one_task): @@ -170,7 +170,7 @@ def test_delete_task_not_found(client): # ***************************************************************** assert Task.query.all() == [] - assert response_body == {"details": "Task"} + assert response_body == {"message": "Task 1 not found" } @pytest.mark.skip(reason="No way to test this feature yet") From 66318364225b2261326ef8a6d346be38ae016343 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Tue, 10 May 2022 19:01:22 -0400 Subject: [PATCH 10/29] fixed instance method and PUT route --- app/models/task.py | 19 +++++++++++++------ app/routes.py | 9 +++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index b1b9fde1f..349c996bf 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -8,12 +8,19 @@ class Task(db.Model): def to_dict(self): - return dict( - id=self.task_id, - title=self.title, - description=self.description, - is_complete=False if self.completed_at == None else self.completed_at - ) + # return dict( + # id=self.task_id, + # title=self.title, + # description=self.description, + # is_complete=False if self.completed_at == None else self.completed_at + # ) + completed_at = True if self.completed_at else False + return { + "id": self.task_id, + "title": self.title, + "description": self.description, + "is_complete": completed_at + } def override_task(self, data_dict): self.title = data_dict["title"] diff --git a/app/routes.py b/app/routes.py index 42427b0f1..984a35a76 100644 --- a/app/routes.py +++ b/app/routes.py @@ -29,14 +29,15 @@ def read_tasks(task_id): @bp.route("/", methods=("PUT",)) def update_task(task_id): - request_body = request.get_json() task = validate_task_id(task_id) + request_body = request.get_json() - updated_task = task.override_task(request_body) - db.session.add(updated_task) + task.title = request_body["title"] + task.description = request_body["description"] + db.session.commit() - return make_response(jsonify({"task": task.to_dict()}, 200)) + return jsonify({"task": task.to_dict()}), 200 @bp.route("/", methods=("DELETE",)) From 507e35f77933160f5b8c626b74d0ec5c96a36186 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Tue, 10 May 2022 19:49:55 -0400 Subject: [PATCH 11/29] refacrored GET route to pass all tests --- app/models/task.py | 21 ++++++++++++++------- app/routes.py | 21 ++++++++++++++------- tests/test_wave_01.py | 10 +++++----- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 349c996bf..36332d73e 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -14,13 +14,20 @@ def to_dict(self): # description=self.description, # is_complete=False if self.completed_at == None else self.completed_at # ) - completed_at = True if self.completed_at else False - return { - "id": self.task_id, - "title": self.title, - "description": self.description, - "is_complete": completed_at - } + return dict( + id=self.task_id, + title=self.title, + description=self.description, + is_complete=True if self.completed_at else False + ) + + # completed_at = True if self.completed_at else False + # return { + # "id": self.task_id, + # "title": self.title, + # "description": self.description, + # "is_complete": completed_at + # } def override_task(self, data_dict): self.title = data_dict["title"] diff --git a/app/routes.py b/app/routes.py index 984a35a76..f82bf5329 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,3 +1,4 @@ +from asyncio import tasks from flask import Blueprint, jsonify, make_response, request, abort from app.models.task import Task from app import db @@ -9,6 +10,9 @@ @bp.route("", methods=("POST",)) def create_task(): request_body = request.get_json() + if "title" not in request_body or "description" not in request_body: + abort(make_response({"details": "Invalid data"}, 400)) + task = Task(title=request_body["title"], description=request_body["description"]) db.session.add(task) @@ -18,15 +22,18 @@ def create_task(): @bp.route("/", methods=("GET",)) -def read_tasks(task_id): - if not task_id: - tasks = Task.query.all() - return jsonify([task.to_dict() for task in tasks]) - +def read_one_task(task_id): task = validate_task_id(task_id) return make_response(jsonify({"task": task.to_dict()}), 200) +@bp.route("", methods=("GET",)) +def read_all_tasks(): + tasks = Task.query.all() + tasks_response = [task.to_dict() for task in tasks] + return make_response(jsonify(tasks_response), 200) + + @bp.route("/", methods=("PUT",)) def update_task(task_id): task = validate_task_id(task_id) @@ -52,10 +59,10 @@ def validate_task_id(task_id): try: task_id = int(task_id) except: - abort(make_response({"message": "Invalid data"}, 400)) + abort(make_response({"details": "Invalid data"}, 400)) task = Task.query.get(task_id) if not task: - abort(make_response({"message": "Task 1 not found"}, 404)) + abort(make_response({"details": "Task 1 not found"}, 404)) return task \ No newline at end of file diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index aac507a27..8caf9d1d9 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -66,7 +66,7 @@ def test_get_task_not_found(client): # ***************************************************************** # how to get task_id into response body? # assert response_body == {"message": f"Task id {task_id} not found"} - assert response_body == {"message": "Task 1 not found"} + assert response_body == {"details": "Task 1 not found"} # @pytest.mark.skip(reason="No way to test this feature yet") @@ -138,7 +138,7 @@ def test_update_task_not_found(client): # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** - assert response_body == {"message": "Task 1 not found"} + assert response_body == {"details": "Task 1 not found"} # @pytest.mark.skip(reason="No way to test this feature yet") def test_delete_task(client, one_task): @@ -170,10 +170,10 @@ def test_delete_task_not_found(client): # ***************************************************************** assert Task.query.all() == [] - assert response_body == {"message": "Task 1 not found" } + assert response_body == {"details": "Task 1 not found" } -@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={ @@ -190,7 +190,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={ From f00d20dc13efd9341104ed53c076d9874754ce67 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Tue, 10 May 2022 21:49:59 -0400 Subject: [PATCH 12/29] added query params to GET route --- app/models/task.py | 1 + app/routes.py | 15 +++++++++++++-- tests/test_wave_02.py | 4 ++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 36332d73e..51c065867 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -21,6 +21,7 @@ def to_dict(self): is_complete=True if self.completed_at else False ) + # completed_at = True if self.completed_at else False # return { # "id": self.task_id, diff --git a/app/routes.py b/app/routes.py index f82bf5329..51c9c28d3 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,5 +1,5 @@ -from asyncio import tasks from flask import Blueprint, jsonify, make_response, request, abort +from sqlalchemy import asc from app.models.task import Task from app import db @@ -29,8 +29,19 @@ def read_one_task(task_id): @bp.route("", methods=("GET",)) def read_all_tasks(): - tasks = Task.query.all() + title_query = request.args.get("sort") + + if title_query == "asc": + tasks = Task.query.order_by(Task.title.asc()) + + elif title_query == "desc": + tasks = Task.query.order_by(Task.title.desc()) + + else: + tasks = Task.query.all() + tasks_response = [task.to_dict() for task in tasks] + return make_response(jsonify(tasks_response), 200) diff --git a/tests/test_wave_02.py b/tests/test_wave_02.py index a087e0909..651e3aebd 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") From c0a44b4c819e09890d9a062af95fd574d3f9cbc7 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Tue, 10 May 2022 22:19:42 -0400 Subject: [PATCH 13/29] refactored POST route --- app/models/task.py | 34 ++++++++-------------------------- app/routes.py | 5 ++--- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 51c065867..08a59594d 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -8,12 +8,6 @@ class Task(db.Model): def to_dict(self): - # return dict( - # id=self.task_id, - # title=self.title, - # description=self.description, - # is_complete=False if self.completed_at == None else self.completed_at - # ) return dict( id=self.task_id, title=self.title, @@ -21,24 +15,12 @@ def to_dict(self): is_complete=True if self.completed_at else False ) - - # completed_at = True if self.completed_at else False - # return { - # "id": self.task_id, - # "title": self.title, - # "description": self.description, - # "is_complete": completed_at - # } + @classmethod + def from_dict(cls, data_dict): + return cls( + title=data_dict["title"], + description=data_dict["description"]) - def override_task(self, data_dict): - self.title = data_dict["title"] - self.description = data_dict["description"] - - # @classmethod - # def from_dict(cls, data_dict): - # return cls( - # dict( - # title=data_dict["title"], - # description=data_dict["description"], - # completed_at=data_dict["completed_at"]) - # ) \ No newline at end of file + # def override_task(self, data_dict): + # self.title = data_dict["title"] + # self.description = data_dict["description"] diff --git a/app/routes.py b/app/routes.py index 51c9c28d3..507a02af1 100644 --- a/app/routes.py +++ b/app/routes.py @@ -13,7 +13,8 @@ def create_task(): if "title" not in request_body or "description" not in request_body: abort(make_response({"details": "Invalid data"}, 400)) - task = Task(title=request_body["title"], description=request_body["description"]) + # task = Task(title=request_body["title"], description=request_body["description"]) + task = Task.from_dict(request_body) db.session.add(task) db.session.commit() @@ -33,10 +34,8 @@ def read_all_tasks(): if title_query == "asc": tasks = Task.query.order_by(Task.title.asc()) - elif title_query == "desc": tasks = Task.query.order_by(Task.title.desc()) - else: tasks = Task.query.all() From 8321582a6c2d884b5e041193b791928107c6e5ca Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Wed, 11 May 2022 01:30:42 -0400 Subject: [PATCH 14/29] added message helper function, ins and cls method to Task --- app/models/task.py | 8 +++++--- app/routes.py | 29 +++++++++++++++++++---------- tests/test_wave_03.py | 2 +- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 08a59594d..c366efa48 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -15,12 +15,14 @@ def to_dict(self): is_complete=True if self.completed_at else False ) + @classmethod def from_dict(cls, data_dict): return cls( title=data_dict["title"], description=data_dict["description"]) - # def override_task(self, data_dict): - # self.title = data_dict["title"] - # self.description = data_dict["description"] + + def override_task(self, data_dict): + self.title = data_dict["title"] + self.description = data_dict["description"] \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 507a02af1..b1692ad3b 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,5 +1,4 @@ from flask import Blueprint, jsonify, make_response, request, abort -from sqlalchemy import asc from app.models.task import Task from app import db @@ -11,9 +10,8 @@ def create_task(): request_body = request.get_json() if "title" not in request_body or "description" not in request_body: - abort(make_response({"details": "Invalid data"}, 400)) + create_message("Invalid data", 400) - # task = Task(title=request_body["title"], description=request_body["description"]) task = Task.from_dict(request_body) db.session.add(task) @@ -45,12 +43,13 @@ def read_all_tasks(): @bp.route("/", methods=("PUT",)) -def update_task(task_id): +def replace_task(task_id): task = validate_task_id(task_id) request_body = request.get_json() - task.title = request_body["title"] - task.description = request_body["description"] + # task.title = request_body["title"] + # task.description = request_body["description"] + task.override_task(request_body) db.session.commit() @@ -62,17 +61,27 @@ def delete_task(task_id): task = validate_task_id(task_id) db.session.delete(task) db.session.commit() - return {"details": f"Task {task_id} \"{task.title}\" successfully deleted"}, 200 + create_message(f"Task {task_id} \"{task.title}\" successfully deleted", 200) + + +@bp.route("/", methods=("PATCH",)) +def update_task(task_id): + task = validate_task_id(task_id) + pass def validate_task_id(task_id): try: task_id = int(task_id) except: - abort(make_response({"details": "Invalid data"}, 400)) + create_message("Invalid data", 400) task = Task.query.get(task_id) if not task: - abort(make_response({"details": "Task 1 not found"}, 404)) - return task \ No newline at end of file + create_message("Task 1 not found", 404) + return task + + +def create_message(details_info, status_code): + abort(make_response({"details": details_info}, status_code)) \ No newline at end of file diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index 959176ceb..ffc49a5b4 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 """ From 6d83c97b5f402f8114ed9cc7dc3bb990b26b370f Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Wed, 11 May 2022 11:11:23 -0400 Subject: [PATCH 15/29] created starter code for PATCH route --- app/routes.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/app/routes.py b/app/routes.py index b1692ad3b..a362c6892 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,6 +1,7 @@ from flask import Blueprint, jsonify, make_response, request, abort from app.models.task import Task from app import db +import datetime bp = Blueprint("tasks", __name__, url_prefix="/tasks") @@ -11,7 +12,7 @@ def create_task(): request_body = request.get_json() if "title" not in request_body or "description" not in request_body: create_message("Invalid data", 400) - + # could change the above to a try and except task = Task.from_dict(request_body) db.session.add(task) @@ -46,9 +47,7 @@ def read_all_tasks(): def replace_task(task_id): task = validate_task_id(task_id) request_body = request.get_json() - - # task.title = request_body["title"] - # task.description = request_body["description"] + # might want to put this into a try and except task.override_task(request_body) db.session.commit() @@ -64,10 +63,22 @@ def delete_task(task_id): create_message(f"Task {task_id} \"{task.title}\" successfully deleted", 200) -@bp.route("/", methods=("PATCH",)) +@bp.route("//mark_complete", methods=("PATCH",)) def update_task(task_id): task = validate_task_id(task_id) - pass + request_body = request.get_json() + task_keys = request_body.keys() + + if "name" in task_keys: + task.name = request_body["name"] + + if "description" in task_keys: + task.description = request_body["description"] + + task.completed_at = datetime.datetime.now() + + db.session.commit() + return jsonify({"task": task.to_dict()}), 200 def validate_task_id(task_id): From 84e7a21b15038ae19e4a11546bb05cc80993dbfd Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Wed, 11 May 2022 20:26:24 -0400 Subject: [PATCH 16/29] created task completion functions --- app/models/task.py | 9 +++++++-- app/routes.py | 20 ++++++++++---------- tests/test_wave_03.py | 14 +++++++------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index c366efa48..465ca2d36 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -4,7 +4,7 @@ class Task(db.Model): task_id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) description = db.Column(db.String) - completed_at = db.Column(db.DateTime, nullable=True) + completed_at = db.Column(db.DateTime, nullable=True, default=None) def to_dict(self): @@ -25,4 +25,9 @@ def from_dict(cls, data_dict): def override_task(self, data_dict): self.title = data_dict["title"] - self.description = data_dict["description"] \ No newline at end of file + self.description = data_dict["description"] + + + + + # is_complete=self.completed_at is not None \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index a362c6892..4b651021c 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,7 +1,7 @@ from flask import Blueprint, jsonify, make_response, request, abort from app.models.task import Task from app import db -import datetime +from datetime import datetime bp = Blueprint("tasks", __name__, url_prefix="/tasks") @@ -64,20 +64,20 @@ def delete_task(task_id): @bp.route("//mark_complete", methods=("PATCH",)) -def update_task(task_id): +def mark_task_complete(task_id): task = validate_task_id(task_id) - request_body = request.get_json() - task_keys = request_body.keys() - - if "name" in task_keys: - task.name = request_body["name"] + task.completed_at = datetime.utcnow() + db.session.commit() - if "description" in task_keys: - task.description = request_body["description"] + return jsonify({"task": task.to_dict()}), 200 - task.completed_at = datetime.datetime.now() +@bp.route("//mark_incomplete", methods=("PATCH",)) +def mark_task_incomplete(task_id): + task = validate_task_id(task_id) + task.completed_at = None db.session.commit() + return jsonify({"task": task.to_dict()}), 200 diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index ffc49a5b4..8added4a4 100644 --- a/tests/test_wave_03.py +++ b/tests/test_wave_03.py @@ -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") @@ -134,7 +134,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") @@ -151,7 +151,7 @@ def test_mark_incomplete_missing_task(client): # Let's add this test for creating tasks, now that # the completion functionality has been implemented -@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_with_valid_completed_at(client): # Act response = client.post("/tasks", json={ @@ -181,7 +181,7 @@ def test_create_task_with_valid_completed_at(client): # Let's add this test for updating tasks, now that # the completion functionality has been implemented -@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_with_completed_at_date(client, completed_task): # Act response = client.put("/tasks/1", json={ From f14d1add143db132a0c9313d782e9d85a1d8cd90 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Wed, 11 May 2022 21:15:57 -0400 Subject: [PATCH 17/29] refactored from_dict --- app/models/task.py | 13 ++++++------- tests/test_wave_03.py | 6 ++++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 465ca2d36..dc9afe7b7 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -1,3 +1,4 @@ +from requests import request from app import db class Task(db.Model): @@ -13,6 +14,7 @@ def to_dict(self): title=self.title, description=self.description, is_complete=True if self.completed_at else False + # is_complete=self.completed_at is not None ) @@ -20,14 +22,11 @@ def to_dict(self): def from_dict(cls, data_dict): return cls( title=data_dict["title"], - description=data_dict["description"]) + description=data_dict["description"], + # completed_at = True if data_dict["completed_at"] else None) + completed_at = data_dict.get("completed_at", None)) def override_task(self, data_dict): self.title = data_dict["title"] - self.description = data_dict["description"] - - - - - # is_complete=self.completed_at is not None \ No newline at end of file + self.description = data_dict["description"] \ No newline at end of file diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index 8added4a4..df339cb88 100644 --- a/tests/test_wave_03.py +++ b/tests/test_wave_03.py @@ -128,10 +128,11 @@ def test_mark_complete_missing_task(client): # Assert assert response.status_code == 404 - 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*************** # ***************************************************************** + assert response_body == {"details": "Task 1 not found"} # @pytest.mark.skip(reason="No way to test this feature yet") @@ -143,10 +144,11 @@ def test_mark_incomplete_missing_task(client): # Assert assert response.status_code == 404 - 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*************** # ***************************************************************** + assert response_body == {"details": "Task 1 not found"} # Let's add this test for creating tasks, now that From 99e989c9f95e5fa0847a80a788ea12dbded87711 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Thu, 12 May 2022 12:05:55 -0400 Subject: [PATCH 18/29] created routes_helper for model --- app/routes.py | 26 ++++---------------------- app/routes_helper.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 app/routes_helper.py diff --git a/app/routes.py b/app/routes.py index 4b651021c..72b2c9277 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,5 +1,6 @@ -from flask import Blueprint, jsonify, make_response, request, abort +from flask import Blueprint, jsonify, request, make_response from app.models.task import Task +from .routes_helper import validate_task_id, create_message from app import db from datetime import datetime @@ -14,7 +15,6 @@ def create_task(): create_message("Invalid data", 400) # could change the above to a try and except task = Task.from_dict(request_body) - db.session.add(task) db.session.commit() @@ -49,7 +49,6 @@ def replace_task(task_id): request_body = request.get_json() # might want to put this into a try and except task.override_task(request_body) - db.session.commit() return jsonify({"task": task.to_dict()}), 200 @@ -60,6 +59,7 @@ def delete_task(task_id): task = validate_task_id(task_id) db.session.delete(task) db.session.commit() + create_message(f"Task {task_id} \"{task.title}\" successfully deleted", 200) @@ -77,22 +77,4 @@ def mark_task_incomplete(task_id): task = validate_task_id(task_id) task.completed_at = None db.session.commit() - - return jsonify({"task": task.to_dict()}), 200 - - -def validate_task_id(task_id): - try: - task_id = int(task_id) - except: - create_message("Invalid data", 400) - - task = Task.query.get(task_id) - - if not task: - create_message("Task 1 not found", 404) - return task - - -def create_message(details_info, status_code): - abort(make_response({"details": details_info}, status_code)) \ No newline at end of file + return jsonify({"task": task.to_dict()}), 200 \ No newline at end of file diff --git a/app/routes_helper.py b/app/routes_helper.py new file mode 100644 index 000000000..e21d553d4 --- /dev/null +++ b/app/routes_helper.py @@ -0,0 +1,18 @@ +from flask import jsonify, make_response, abort +from app.models.task import Task + +def validate_task_id(task_id): + try: + task_id = int(task_id) + except: + create_message("Invalid data", 400) + + task = Task.query.get(task_id) + + if not task: + create_message("Task 1 not found", 404) + return task + + +def create_message(details_info, status_code): + abort(make_response({"details": details_info}, status_code)) \ No newline at end of file From a511f8442a479ead358c4cbb5750b34620a43e4f Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Thu, 12 May 2022 16:40:47 -0400 Subject: [PATCH 19/29] added goal routes, modified goal attributes --- app/__init__.py | 4 +++- app/goal_routes.py | 21 +++++++++++++++++++++ app/models/goal.py | 8 ++++++++ app/models/task.py | 9 +++++---- app/routes_helper.py | 17 ++++++++++++++++- app/{routes.py => task_routes.py} | 18 ++++++++++++++++-- tests/test_wave_01.py | 7 ++++--- tests/test_wave_03.py | 4 ++-- tests/test_wave_05.py | 2 +- 9 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 app/goal_routes.py rename app/{routes.py => task_routes.py} (80%) diff --git a/app/__init__.py b/app/__init__.py index e253d096f..110e422b7 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,7 +30,9 @@ def create_app(test_config=None): migrate.init_app(app, db) # Register Blueprints here - from .routes import bp + from .task_routes import bp app.register_blueprint(bp) + # from .goal_routes import bp + # app.register_blueprint(bp) return app diff --git a/app/goal_routes.py b/app/goal_routes.py new file mode 100644 index 000000000..670bf6fb3 --- /dev/null +++ b/app/goal_routes.py @@ -0,0 +1,21 @@ +from flask import Blueprint, jsonify, make_response, request +from app.models.task import Task +from app.models.goal import Goal +from .routes_helper import validate_goal_id, create_message +from app import db + + +bp = Blueprint("goals", __name__, url_prefix="/goals") + + +@bp.route("", methods=("POST",)) +def create_goal(): + request_body = request.get_json() + if "title" not in request_body: + create_message("Invalid data", 400) + + goal = Goal.from_dict(request_body) + db.session.add(goal) + db.session.commit() + + return make_response(jsonify({"goal.from_dict()}), 201) \ No newline at end of file diff --git a/app/models/goal.py b/app/models/goal.py index b0ed11dd8..1c77c9a6c 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -3,3 +3,11 @@ class Goal(db.Model): goal_id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) + + + @classmethod + def from_dict(cls, data_dict): + return cls( + title=data_dict["title"], + ) \ No newline at end of file diff --git a/app/models/task.py b/app/models/task.py index dc9afe7b7..e225e24ff 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -1,6 +1,7 @@ from requests import request from app import db + class Task(db.Model): task_id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) @@ -13,8 +14,8 @@ def to_dict(self): id=self.task_id, title=self.title, description=self.description, - is_complete=True if self.completed_at else False - # is_complete=self.completed_at is not None + # is_complete=True if self.completed_at else False + is_complete=self.completed_at is not None ) @@ -23,8 +24,8 @@ def from_dict(cls, data_dict): return cls( title=data_dict["title"], description=data_dict["description"], - # completed_at = True if data_dict["completed_at"] else None) - completed_at = data_dict.get("completed_at", None)) + completed_at = data_dict.get("completed_at", None) + ) def override_task(self, data_dict): diff --git a/app/routes_helper.py b/app/routes_helper.py index e21d553d4..9111b5e67 100644 --- a/app/routes_helper.py +++ b/app/routes_helper.py @@ -1,5 +1,7 @@ from flask import jsonify, make_response, abort from app.models.task import Task +from app.models.goal import Goal + def validate_task_id(task_id): try: @@ -10,9 +12,22 @@ def validate_task_id(task_id): task = Task.query.get(task_id) if not task: - create_message("Task 1 not found", 404) + create_message("Task not found", 404) return task +def validate_goal_id(goal_id): + try: + goal_id = int(goal_id) + except: + create_message("Invalid data", 400) + + goal = Goal.query.get(goal_id) + + if not goal: + create_message("Goal not found", 404) + return goal + + def create_message(details_info, status_code): abort(make_response({"details": details_info}, status_code)) \ No newline at end of file diff --git a/app/routes.py b/app/task_routes.py similarity index 80% rename from app/routes.py rename to app/task_routes.py index 72b2c9277..09a7dcb6d 100644 --- a/app/routes.py +++ b/app/task_routes.py @@ -1,8 +1,9 @@ -from flask import Blueprint, jsonify, request, make_response +from flask import Blueprint, jsonify, make_response, request from app.models.task import Task from .routes_helper import validate_task_id, create_message from app import db from datetime import datetime +import requests, os bp = Blueprint("tasks", __name__, url_prefix="/tasks") @@ -60,15 +61,28 @@ def delete_task(task_id): db.session.delete(task) db.session.commit() - create_message(f"Task {task_id} \"{task.title}\" successfully deleted", 200) + create_message(f'Task {task_id} "{task.title}" successfully deleted', 200) @bp.route("//mark_complete", methods=("PATCH",)) def mark_task_complete(task_id): task = validate_task_id(task_id) + + # task.title = "My Beautiful Task" + # task.description = "What a pretty task!" task.completed_at = datetime.utcnow() + db.session.commit() + requests.post( + url="https://slack.com/api/chat.postMessage", + data={ + "channel": "task-notifications", + "text": f"Someone just completed the task {task.title}" + }, + headers={"Authorization": os.environ.get("token")}, + ) + return jsonify({"task": task.to_dict()}), 200 diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index 8caf9d1d9..f563e386e 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -66,7 +66,7 @@ def test_get_task_not_found(client): # ***************************************************************** # how to get task_id into response body? # assert response_body == {"message": f"Task id {task_id} not found"} - assert response_body == {"details": "Task 1 not found"} + assert response_body == {"details": "Task not found"} # @pytest.mark.skip(reason="No way to test this feature yet") @@ -138,7 +138,8 @@ def test_update_task_not_found(client): # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** - assert response_body == {"details": "Task 1 not found"} + assert response_body == {"details": "Task not found"} + # @pytest.mark.skip(reason="No way to test this feature yet") def test_delete_task(client, one_task): @@ -170,7 +171,7 @@ def test_delete_task_not_found(client): # ***************************************************************** assert Task.query.all() == [] - assert response_body == {"details": "Task 1 not found" } + assert response_body == {"details": "Task not found" } # @pytest.mark.skip(reason="No way to test this feature yet") diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index df339cb88..3cee3612e 100644 --- a/tests/test_wave_03.py +++ b/tests/test_wave_03.py @@ -132,7 +132,7 @@ def test_mark_complete_missing_task(client): # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** - assert response_body == {"details": "Task 1 not found"} + assert response_body == {"details": "Task not found"} # @pytest.mark.skip(reason="No way to test this feature yet") @@ -148,7 +148,7 @@ def test_mark_incomplete_missing_task(client): # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** - assert response_body == {"details": "Task 1 not found"} + assert response_body == {"details": "Task not found"} # Let's add this test for creating tasks, now that diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index aee7c52a1..8e451f908 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") From c015c33a92666b658b62b5d2419b760eb83022c2 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Thu, 12 May 2022 16:41:37 -0400 Subject: [PATCH 20/29] modified goal routes --- 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 670bf6fb3..2c9d25806 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -18,4 +18,4 @@ def create_goal(): db.session.add(goal) db.session.commit() - return make_response(jsonify({"goal.from_dict()}), 201) \ No newline at end of file + return make_response(jsonify(), 201) \ No newline at end of file From 62c5100fc34df56cee7dde4a570d5246dabaf6c8 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Thu, 12 May 2022 17:25:54 -0400 Subject: [PATCH 21/29] added GET routes for goals --- app/__init__.py | 5 +++-- app/goal_routes.py | 15 ++++++++++++++- app/models/goal.py | 11 +++++++++-- migrations/versions/8b9c6262707c_.py | 28 ++++++++++++++++++++++++++++ tests/test_wave_05.py | 4 ++-- 5 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 migrations/versions/8b9c6262707c_.py diff --git a/app/__init__.py b/app/__init__.py index 110e422b7..d3b580808 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -33,6 +33,7 @@ def create_app(test_config=None): from .task_routes import bp app.register_blueprint(bp) - # from .goal_routes import bp - # app.register_blueprint(bp) + from .goal_routes import bp + app.register_blueprint(bp) + return app diff --git a/app/goal_routes.py b/app/goal_routes.py index 2c9d25806..5028de896 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -18,4 +18,17 @@ def create_goal(): db.session.add(goal) db.session.commit() - return make_response(jsonify(), 201) \ No newline at end of file + return make_response(jsonify({"goal": goal.to_dict()}), 201) + + +@bp.route("/", methods=("GET",)) +def read_goal(goal_id): + goal = validate_goal_id(goal_id) + return make_response(jsonify({"goal": goal.to_dict()}), 200) + + +@bp.route("", methods=("GET",)) +def real_all_goals(): + goals = Goal.query.all() + goals_response = [task.to_dict() for task in goals] + return make_response(jsonify(goals_response), 200) \ No newline at end of file diff --git a/app/models/goal.py b/app/models/goal.py index 1c77c9a6c..195913bac 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -9,5 +9,12 @@ class Goal(db.Model): @classmethod def from_dict(cls, data_dict): return cls( - title=data_dict["title"], - ) \ No newline at end of file + title=data_dict["title"], + ) + + + def to_dict(self): + return dict( + id=self.goal_id, + title=self.title + ) \ No newline at end of file diff --git a/migrations/versions/8b9c6262707c_.py b/migrations/versions/8b9c6262707c_.py new file mode 100644 index 000000000..1a10828c5 --- /dev/null +++ b/migrations/versions/8b9c6262707c_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 8b9c6262707c +Revises: a2ea7a98b436 +Create Date: 2022-05-12 17:01:14.130379 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '8b9c6262707c' +down_revision = 'a2ea7a98b436' +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/tests/test_wave_05.py b/tests/test_wave_05.py index 8e451f908..6784e8a3b 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -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") From 77e105cd3014e548fb56654dedbb99eabff8e172 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Thu, 12 May 2022 20:58:30 -0400 Subject: [PATCH 22/29] created assertions for wave 5 --- app/goal_routes.py | 21 ++++++++++++++++++- app/models/goal.py | 10 ++++++--- app/task_routes.py | 4 ---- tests/test_wave_01.py | 3 --- tests/test_wave_05.py | 47 ++++++++++++++++++++++++++++++++----------- 5 files changed, 62 insertions(+), 23 deletions(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index 5028de896..8f622f2c3 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -31,4 +31,23 @@ def read_goal(goal_id): def real_all_goals(): goals = Goal.query.all() goals_response = [task.to_dict() for task in goals] - return make_response(jsonify(goals_response), 200) \ No newline at end of file + return make_response(jsonify(goals_response), 200) + + +@bp.route("/", methods=("PUT",)) +def replace_goal(goal_id): + goal = validate_goal_id(goal_id) + request_body = request.get_json() + goal.override_goal(request_body) + db.session.commit() + + return jsonify({"goal": goal.to_dict()}), 200 + + +@bp.route("/", methods=("DELETE",)) +def delete_goal(goal_id): + goal = validate_goal_id(goal_id) + db.session.delete(goal) + db.session.commit() + + create_message(f'Goal {goal_id} "{goal.title}" successfully deleted', 200) \ No newline at end of file diff --git a/app/models/goal.py b/app/models/goal.py index 195913bac..a85fd27a9 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -9,12 +9,16 @@ class Goal(db.Model): @classmethod def from_dict(cls, data_dict): return cls( - title=data_dict["title"], - ) + title=data_dict["title"] + ) def to_dict(self): return dict( id=self.goal_id, title=self.title - ) \ No newline at end of file + ) + + + def override_goal(self, data_dict): + self.title = data_dict["title"] \ No newline at end of file diff --git a/app/task_routes.py b/app/task_routes.py index 09a7dcb6d..8b085e81f 100644 --- a/app/task_routes.py +++ b/app/task_routes.py @@ -67,11 +67,7 @@ def delete_task(task_id): @bp.route("//mark_complete", methods=("PATCH",)) def mark_task_complete(task_id): task = validate_task_id(task_id) - - # task.title = "My Beautiful Task" - # task.description = "What a pretty task!" task.completed_at = datetime.utcnow() - db.session.commit() requests.post( diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index f563e386e..c9c2699aa 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -64,8 +64,6 @@ def test_get_task_not_found(client): # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** - # how to get task_id into response body? - # assert response_body == {"message": f"Task id {task_id} not found"} assert response_body == {"details": "Task not found"} @@ -169,7 +167,6 @@ def test_delete_task_not_found(client): # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** - assert Task.query.all() == [] assert response_body == {"details": "Task not found" } diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index 6784e8a3b..6e7eb1df6 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -1,5 +1,5 @@ import pytest - +from app.models.goal import Goal # @pytest.mark.skip(reason="No way to test this feature yet") def test_get_goals_no_saved_goals(client): @@ -46,22 +46,24 @@ 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 == {"details": "Goal not found"} # ---- 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={ @@ -80,34 +82,53 @@ 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": "This was my tutor's idea" + }) + 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 + assert response_body == { + "goal": { + "id": 1, + "title": "This was my tutor's idea" + } + } + goal = Goal.query.get(1) # assertion 3 goes here + assert goal.title == "This was my tutor's idea" # ---- 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": "This was my tutor's idea" + }) + 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 == {"details": "Goal not found"} # ---- 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") @@ -124,10 +145,12 @@ def test_delete_goal(client, one_goal): response = client.get("/goals/1") assert response.status_code == 404 - 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*************** # ***************************************************************** + assert Goal.query.get(1) == None + assert response_body == {"details": f'Goal 1 "Build a habit of going outside daily" successfully deleted'} @pytest.mark.skip(reason="test to be completed by student") @@ -144,7 +167,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={}) From 9250b1b4cf9f7b8dc1bf48717fe0707ab3a96a6b Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Thu, 12 May 2022 22:42:21 -0400 Subject: [PATCH 23/29] updated Goal and Task models --- app/goal_routes.py | 2 +- app/models/goal.py | 1 + app/models/task.py | 2 ++ app/routes_helper.py | 4 ++-- migrations/versions/b555e21d83b7_.py | 30 ++++++++++++++++++++++++++++ tests/test_wave_01.py | 2 +- tests/test_wave_05.py | 11 +++++++--- 7 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 migrations/versions/b555e21d83b7_.py diff --git a/app/goal_routes.py b/app/goal_routes.py index 8f622f2c3..ea5377c6d 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -1,5 +1,5 @@ from flask import Blueprint, jsonify, make_response, request -from app.models.task import Task +# from app.models.task import Task from app.models.goal import Goal from .routes_helper import validate_goal_id, create_message from app import db diff --git a/app/models/goal.py b/app/models/goal.py index a85fd27a9..3f341113f 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -4,6 +4,7 @@ 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) @classmethod diff --git a/app/models/task.py b/app/models/task.py index e225e24ff..3b1340ed0 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -7,6 +7,8 @@ class Task(db.Model): title = db.Column(db.String) description = db.Column(db.String) completed_at = db.Column(db.DateTime, nullable=True, default=None) + goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id')) + goal = db.relationship("Goal", back_populates="tasks") def to_dict(self): diff --git a/app/routes_helper.py b/app/routes_helper.py index 9111b5e67..caef52735 100644 --- a/app/routes_helper.py +++ b/app/routes_helper.py @@ -1,8 +1,8 @@ -from flask import jsonify, make_response, abort +from flask import make_response, abort from app.models.task import Task from app.models.goal import Goal - +# maybe the ids can reactored into 1 function? def validate_task_id(task_id): try: task_id = int(task_id) diff --git a/migrations/versions/b555e21d83b7_.py b/migrations/versions/b555e21d83b7_.py new file mode 100644 index 000000000..7c766680c --- /dev/null +++ b/migrations/versions/b555e21d83b7_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: b555e21d83b7 +Revises: 8b9c6262707c +Create Date: 2022-05-12 22:38:56.069991 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b555e21d83b7' +down_revision = '8b9c6262707c' +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/tests/test_wave_01.py b/tests/test_wave_01.py index c9c2699aa..d69ade512 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -65,6 +65,7 @@ def test_get_task_not_found(client): # **Complete test with assertion about response body*************** # ***************************************************************** assert response_body == {"details": "Task not found"} + assert Task.query.all() == [] # @pytest.mark.skip(reason="No way to test this feature yet") @@ -167,7 +168,6 @@ def test_delete_task_not_found(client): # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** - assert Task.query.all() == [] assert response_body == {"details": "Task not found" } diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index 6e7eb1df6..74a43e002 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -60,6 +60,7 @@ def test_get_goal_not_found(client): assert response.status_code == 404 # assertion 2 goes here assert response_body == {"details": "Goal not found"} + assert Goal.query.all() == [] # ---- Complete Test ---- @@ -153,17 +154,21 @@ def test_delete_goal(client, one_goal): assert response_body == {"details": f'Goal 1 "Build a habit of going outside daily" successfully deleted'} -@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 Goal.query.get(1) == None # assertion 2 goes here + assert response.status_code == 404 + assert response_body == {"details": "Goal not found"} # ---- Complete Assertions Here ---- From 582d47641d89b753dff641640f97dcde3633418a Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Fri, 13 May 2022 01:53:50 -0400 Subject: [PATCH 24/29] refactored create_message --- app/goal_routes.py | 20 ++++++++++++++++++-- app/models/task.py | 1 - app/routes_helper.py | 2 +- app/task_routes.py | 14 ++++++-------- tests/test_wave_06.py | 2 +- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index ea5377c6d..f48ff36f9 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -1,7 +1,7 @@ from flask import Blueprint, jsonify, make_response, request # from app.models.task import Task from app.models.goal import Goal -from .routes_helper import validate_goal_id, create_message +from .routes_helper import validate_goal_id, create_message, validate_task_id from app import db @@ -21,6 +21,22 @@ def create_goal(): return make_response(jsonify({"goal": goal.to_dict()}), 201) +# @bp.route("//tasks", methods=("POST",)) +# def create_goal_from_tasks(goal_id): +# goal = validate_goal_id(goal_id) +# request_body = request.get_json() + +# for task_id in request_body["task_ids"]: +# task = validate_task_id(task_id) +# task.goal = goal + +# # db.session.add(task) +# db.session.commit() +# pass +# # return make_response(jsonify(task.to_dict()), 200) + + + @bp.route("/", methods=("GET",)) def read_goal(goal_id): goal = validate_goal_id(goal_id) @@ -30,7 +46,7 @@ def read_goal(goal_id): @bp.route("", methods=("GET",)) def real_all_goals(): goals = Goal.query.all() - goals_response = [task.to_dict() for task in goals] + goals_response = [goal.to_dict() for goal in goals] return make_response(jsonify(goals_response), 200) diff --git a/app/models/task.py b/app/models/task.py index 3b1340ed0..82615a104 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -16,7 +16,6 @@ def to_dict(self): id=self.task_id, title=self.title, description=self.description, - # is_complete=True if self.completed_at else False is_complete=self.completed_at is not None ) diff --git a/app/routes_helper.py b/app/routes_helper.py index caef52735..e12289800 100644 --- a/app/routes_helper.py +++ b/app/routes_helper.py @@ -29,5 +29,5 @@ def validate_goal_id(goal_id): return goal -def create_message(details_info, status_code): +def create_message(details_info, status_code=200): abort(make_response({"details": details_info}, status_code)) \ No newline at end of file diff --git a/app/task_routes.py b/app/task_routes.py index 8b085e81f..e0fd569e2 100644 --- a/app/task_routes.py +++ b/app/task_routes.py @@ -14,7 +14,6 @@ def create_task(): request_body = request.get_json() if "title" not in request_body or "description" not in request_body: create_message("Invalid data", 400) - # could change the above to a try and except task = Task.from_dict(request_body) db.session.add(task) db.session.commit() @@ -25,7 +24,7 @@ def create_task(): @bp.route("/", methods=("GET",)) def read_one_task(task_id): task = validate_task_id(task_id) - return make_response(jsonify({"task": task.to_dict()}), 200) + return make_response(jsonify({"task": task.to_dict()})) @bp.route("", methods=("GET",)) @@ -41,18 +40,17 @@ def read_all_tasks(): tasks_response = [task.to_dict() for task in tasks] - return make_response(jsonify(tasks_response), 200) + return make_response(jsonify(tasks_response)) @bp.route("/", methods=("PUT",)) def replace_task(task_id): task = validate_task_id(task_id) request_body = request.get_json() - # might want to put this into a try and except task.override_task(request_body) db.session.commit() - return jsonify({"task": task.to_dict()}), 200 + return jsonify({"task": task.to_dict()}) @bp.route("/", methods=("DELETE",)) @@ -61,7 +59,7 @@ def delete_task(task_id): db.session.delete(task) db.session.commit() - create_message(f'Task {task_id} "{task.title}" successfully deleted', 200) + create_message(f'Task {task_id} "{task.title}" successfully deleted') @bp.route("//mark_complete", methods=("PATCH",)) @@ -79,7 +77,7 @@ def mark_task_complete(task_id): headers={"Authorization": os.environ.get("token")}, ) - return jsonify({"task": task.to_dict()}), 200 + return jsonify({"task": task.to_dict()}) @bp.route("//mark_incomplete", methods=("PATCH",)) @@ -87,4 +85,4 @@ def mark_task_incomplete(task_id): task = validate_task_id(task_id) task.completed_at = None db.session.commit() - return jsonify({"task": task.to_dict()}), 200 \ No newline at end of file + return jsonify({"task": task.to_dict()}) \ No newline at end of file diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index 8afa4325e..566c52a39 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={ From 15a79a8055a1be3d14c77b27d5be074f833d37ea Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Fri, 13 May 2022 02:33:19 -0400 Subject: [PATCH 25/29] completed POST route for adding tasks to goals --- app/goal_routes.py | 29 ++++++++++++++--------------- app/routes_helper.py | 2 +- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index f48ff36f9..3c19450c4 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -21,21 +21,20 @@ def create_goal(): return make_response(jsonify({"goal": goal.to_dict()}), 201) -# @bp.route("//tasks", methods=("POST",)) -# def create_goal_from_tasks(goal_id): -# goal = validate_goal_id(goal_id) -# request_body = request.get_json() - -# for task_id in request_body["task_ids"]: -# task = validate_task_id(task_id) -# task.goal = goal - -# # db.session.add(task) -# db.session.commit() -# pass -# # return make_response(jsonify(task.to_dict()), 200) - +@bp.route("//tasks", methods=("POST",)) +def add_tasks_to_goal(goal_id): + goal = validate_goal_id(goal_id) + request_body = request.get_json() + tasks_list = [] + for task_id in request_body["task_ids"]: + task = validate_task_id(task_id) + task.goal = goal + tasks_list.append(task.task_id) + + db.session.commit() + return make_response(jsonify({"id": goal.goal_id, "task_ids": tasks_list})) + @bp.route("/", methods=("GET",)) def read_goal(goal_id): @@ -44,7 +43,7 @@ def read_goal(goal_id): @bp.route("", methods=("GET",)) -def real_all_goals(): +def read_all_goals(): goals = Goal.query.all() goals_response = [goal.to_dict() for goal in goals] return make_response(jsonify(goals_response), 200) diff --git a/app/routes_helper.py b/app/routes_helper.py index e12289800..ae0056534 100644 --- a/app/routes_helper.py +++ b/app/routes_helper.py @@ -2,7 +2,7 @@ from app.models.task import Task from app.models.goal import Goal -# maybe the ids can reactored into 1 function? + def validate_task_id(task_id): try: task_id = int(task_id) From bf93f05cd2c50c11028d8aba0e862a8452ad5530 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Fri, 13 May 2022 02:59:58 -0400 Subject: [PATCH 26/29] added assertion for getting goals with no tasks --- app/goal_routes.py | 19 ++++++++++++++----- tests/test_wave_06.py | 7 ++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index 3c19450c4..a7dfbf28d 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -33,20 +33,29 @@ def add_tasks_to_goal(goal_id): tasks_list.append(task.task_id) db.session.commit() - return make_response(jsonify({"id": goal.goal_id, "task_ids": tasks_list})) + return make_response(jsonify({"id": goal.goal_id, + "task_ids": tasks_list})) @bp.route("/", methods=("GET",)) def read_goal(goal_id): goal = validate_goal_id(goal_id) - return make_response(jsonify({"goal": goal.to_dict()}), 200) + return make_response(jsonify({"goal": goal.to_dict()})) @bp.route("", methods=("GET",)) def read_all_goals(): goals = Goal.query.all() goals_response = [goal.to_dict() for goal in goals] - return make_response(jsonify(goals_response), 200) + return make_response(jsonify(goals_response)) + + +@bp.route("//tasks", methods=("GET",)) +def read_specific_goal_tasks(goal_id): + goal = validate_goal_id(goal_id) + task = validate_task_id + goal_tasks = [task for task in goal.tasks] + pass @bp.route("/", methods=("PUT",)) @@ -56,7 +65,7 @@ def replace_goal(goal_id): goal.override_goal(request_body) db.session.commit() - return jsonify({"goal": goal.to_dict()}), 200 + return jsonify({"goal": goal.to_dict()}) @bp.route("/", methods=("DELETE",)) @@ -65,4 +74,4 @@ def delete_goal(goal_id): db.session.delete(goal) db.session.commit() - create_message(f'Goal {goal_id} "{goal.title}" successfully deleted', 200) \ No newline at end of file + create_message(f'Goal {goal_id} "{goal.title}" successfully deleted') \ No newline at end of file diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index 566c52a39..40474ca94 100644 --- a/tests/test_wave_06.py +++ b/tests/test_wave_06.py @@ -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") @@ -51,10 +51,11 @@ def test_get_tasks_for_specific_goal_no_goal(client): # Assert assert response.status_code == 404 - 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*************** # ***************************************************************** + assert response_body == {"details": "Goal not found"} @pytest.mark.skip(reason="No way to test this feature yet") From f3a7cf34bb208185dfc8b96d71607b75e03cd521 Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Fri, 13 May 2022 12:24:07 -0400 Subject: [PATCH 27/29] finished project --- app/goal_routes.py | 11 +++++++---- app/models/task.py | 21 +++++++++++++++------ tests/test_wave_06.py | 7 ++++--- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index a7dfbf28d..38f0ad4a9 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -1,5 +1,5 @@ from flask import Blueprint, jsonify, make_response, request -# from app.models.task import Task +from app.models.task import Task from app.models.goal import Goal from .routes_helper import validate_goal_id, create_message, validate_task_id from app import db @@ -53,9 +53,12 @@ def read_all_goals(): @bp.route("//tasks", methods=("GET",)) def read_specific_goal_tasks(goal_id): goal = validate_goal_id(goal_id) - task = validate_task_id - goal_tasks = [task for task in goal.tasks] - pass + + goal_tasks = [Task.to_dict(task) for task in goal.tasks] + + return make_response(jsonify({"id": goal.goal_id, + "title": goal.title, "tasks": goal_tasks})) + @bp.route("/", methods=("PUT",)) diff --git a/app/models/task.py b/app/models/task.py index 82615a104..2dc1cca38 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -12,12 +12,21 @@ class Task(db.Model): def to_dict(self): - return dict( - id=self.task_id, - title=self.title, - description=self.description, - is_complete=self.completed_at is not None - ) + if self.goal_id is None: + return dict( + id=self.task_id, + title=self.title, + description=self.description, + is_complete=self.completed_at is not None + ) + else: + return dict( + id=self.task_id, + title=self.title, + description=self.description, + goal_id=self.goal_id, + is_complete=self.completed_at is not None + ) @classmethod diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index 40474ca94..bea44514d 100644 --- a/tests/test_wave_06.py +++ b/tests/test_wave_06.py @@ -55,10 +55,11 @@ def test_get_tasks_for_specific_goal_no_goal(client): # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** + assert len(response_body) == 1 assert response_body == {"details": "Goal not found"} -@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 +76,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 +101,7 @@ 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 2758c5dfbc0c2cd1ad063ea9df1d29264947491c Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Fri, 13 May 2022 13:26:58 -0400 Subject: [PATCH 28/29] added Procfile --- Procfile | 1 + 1 file changed, 1 insertion(+) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..62e430aca --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: gunicorn 'app:create_app()' \ No newline at end of file From ce9e220be4d72a604f89eedc8ef3f472315e778b Mon Sep 17 00:00:00 2001 From: Esther Annorzie Date: Fri, 20 May 2022 15:49:38 -0400 Subject: [PATCH 29/29] Refactored Models and routes --- app/goal_routes.py | 28 ++++++++++++++++------------ app/models/goal.py | 8 ++++---- app/models/task.py | 18 ++++++------------ app/routes_helper.py | 27 ++++++++++++++++++++------- app/task_routes.py | 19 ++++++++++++------- tests/test_wave_01.py | 4 ++-- tests/test_wave_05.py | 1 - 7 files changed, 60 insertions(+), 45 deletions(-) diff --git a/app/goal_routes.py b/app/goal_routes.py index 38f0ad4a9..9a9547333 100644 --- a/app/goal_routes.py +++ b/app/goal_routes.py @@ -1,7 +1,7 @@ from flask import Blueprint, jsonify, make_response, request from app.models.task import Task from app.models.goal import Goal -from .routes_helper import validate_goal_id, create_message, validate_task_id +from .routes_helper import get_record_by_id, abort_message from app import db @@ -12,7 +12,7 @@ def create_goal(): request_body = request.get_json() if "title" not in request_body: - create_message("Invalid data", 400) + abort_message("Missing title", 400) goal = Goal.from_dict(request_body) db.session.add(goal) @@ -23,12 +23,12 @@ def create_goal(): @bp.route("//tasks", methods=("POST",)) def add_tasks_to_goal(goal_id): - goal = validate_goal_id(goal_id) + goal = get_record_by_id(goal_id) request_body = request.get_json() tasks_list = [] for task_id in request_body["task_ids"]: - task = validate_task_id(task_id) + task = get_record_by_id(task_id) task.goal = goal tasks_list.append(task.task_id) @@ -39,7 +39,7 @@ def add_tasks_to_goal(goal_id): @bp.route("/", methods=("GET",)) def read_goal(goal_id): - goal = validate_goal_id(goal_id) + goal = get_record_by_id(goal_id) return make_response(jsonify({"goal": goal.to_dict()})) @@ -52,19 +52,23 @@ def read_all_goals(): @bp.route("//tasks", methods=("GET",)) def read_specific_goal_tasks(goal_id): - goal = validate_goal_id(goal_id) + goal = get_record_by_id(goal_id) goal_tasks = [Task.to_dict(task) for task in goal.tasks] - return make_response(jsonify({"id": goal.goal_id, - "title": goal.title, "tasks": goal_tasks})) + return make_response(jsonify({ + "id": goal.goal_id, + "title": goal.title, + "tasks": goal_tasks}) + ) - @bp.route("/", methods=("PUT",)) def replace_goal(goal_id): - goal = validate_goal_id(goal_id) + goal = get_record_by_id(Goal, goal_id) request_body = request.get_json() + if "title" not in request_body: + abort_message("Title not found", 404) goal.override_goal(request_body) db.session.commit() @@ -73,8 +77,8 @@ def replace_goal(goal_id): @bp.route("/", methods=("DELETE",)) def delete_goal(goal_id): - goal = validate_goal_id(goal_id) + goal = get_record_by_id(goal_id) db.session.delete(goal) db.session.commit() - create_message(f'Goal {goal_id} "{goal.title}" successfully deleted') \ No newline at end of file + abort_message(f'Goal {goal_id} "{goal.title}" successfully deleted') \ No newline at end of file diff --git a/app/models/goal.py b/app/models/goal.py index 3f341113f..47ab5d7a6 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -4,20 +4,20 @@ 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) + tasks = db.relationship("Task", back_populates="goal") @classmethod def from_dict(cls, data_dict): return cls( - title=data_dict["title"] + title=data_dict["title"] ) def to_dict(self): return dict( - id=self.goal_id, - title=self.title + id=self.goal_id, + title=self.title ) diff --git a/app/models/task.py b/app/models/task.py index 2dc1cca38..b13a88899 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -1,4 +1,3 @@ -from requests import request from app import db @@ -12,23 +11,19 @@ class Task(db.Model): def to_dict(self): - if self.goal_id is None: - return dict( + response_dict = dict( id=self.task_id, title=self.title, description=self.description, is_complete=self.completed_at is not None ) - else: - return dict( - id=self.task_id, - title=self.title, - description=self.description, - goal_id=self.goal_id, - is_complete=self.completed_at is not None - ) + if self.goal_id: + response_dict["goal_id"] = self.goal_id + + return response_dict + # create similar functions for task and goal, class for creation @classmethod def from_dict(cls, data_dict): return cls( @@ -37,7 +32,6 @@ def from_dict(cls, data_dict): completed_at = data_dict.get("completed_at", None) ) - def override_task(self, data_dict): self.title = data_dict["title"] self.description = data_dict["description"] \ No newline at end of file diff --git a/app/routes_helper.py b/app/routes_helper.py index ae0056534..87d889424 100644 --- a/app/routes_helper.py +++ b/app/routes_helper.py @@ -3,31 +3,44 @@ from app.models.goal import Goal -def validate_task_id(task_id): +def get_task_by_id(task_id): try: task_id = int(task_id) except: - create_message("Invalid data", 400) + abort_message("Invalid data", 400) task = Task.query.get(task_id) if not task: - create_message("Task not found", 404) + abort_message("Task not found", 404) return task -def validate_goal_id(goal_id): +def get_goal_by_id(goal_id): try: goal_id = int(goal_id) except: - create_message("Invalid data", 400) + abort_message("Invalid data", 400) goal = Goal.query.get(goal_id) if not goal: - create_message("Goal not found", 404) + abort_message("Goal not found", 404) return goal -def create_message(details_info, status_code=200): +def get_record_by_id(cls, record_id): + try: + record_id = int(record_id) + except: + abort_message("Invalid data", 400) + + record = cls.query.get(record_id) + + if not record: + abort_message(f"{type(cls).__name__} not found", 404) + return record + + +def abort_message(details_info, status_code=200): abort(make_response({"details": details_info}, status_code)) \ No newline at end of file diff --git a/app/task_routes.py b/app/task_routes.py index e0fd569e2..cb1e6169c 100644 --- a/app/task_routes.py +++ b/app/task_routes.py @@ -1,6 +1,6 @@ from flask import Blueprint, jsonify, make_response, request from app.models.task import Task -from .routes_helper import validate_task_id, create_message +from .routes_helper import abort_message, get_record_by_id, create_message from app import db from datetime import datetime import requests, os @@ -13,7 +13,7 @@ def create_task(): request_body = request.get_json() if "title" not in request_body or "description" not in request_body: - create_message("Invalid data", 400) + create_message("Title or description missing", 400) task = Task.from_dict(request_body) db.session.add(task) db.session.commit() @@ -23,7 +23,7 @@ def create_task(): @bp.route("/", methods=("GET",)) def read_one_task(task_id): - task = validate_task_id(task_id) + task = get_record_by_id(task_id) return make_response(jsonify({"task": task.to_dict()})) @@ -45,8 +45,13 @@ def read_all_tasks(): @bp.route("/", methods=("PUT",)) def replace_task(task_id): - task = validate_task_id(task_id) + task = get_record_by_id(task_id) request_body = request.get_json() + if "title" not in request_body: + abort_message("Title missing", 404) + if "description" not in request_body: + abort_message("Description missing", 404) + task.override_task(request_body) db.session.commit() @@ -55,7 +60,7 @@ def replace_task(task_id): @bp.route("/", methods=("DELETE",)) def delete_task(task_id): - task = validate_task_id(task_id) + task = get_record_by_id(task_id) db.session.delete(task) db.session.commit() @@ -64,7 +69,7 @@ def delete_task(task_id): @bp.route("//mark_complete", methods=("PATCH",)) def mark_task_complete(task_id): - task = validate_task_id(task_id) + task = get_record_by_id(task_id) task.completed_at = datetime.utcnow() db.session.commit() @@ -82,7 +87,7 @@ def mark_task_complete(task_id): @bp.route("//mark_incomplete", methods=("PATCH",)) def mark_task_incomplete(task_id): - task = validate_task_id(task_id) + task = get_record_by_id(task_id) task.completed_at = None db.session.commit() return jsonify({"task": task.to_dict()}) \ No newline at end of file diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index d69ade512..2ec13a8a9 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -138,7 +138,7 @@ def test_update_task_not_found(client): # **Complete test with assertion about response body*************** # ***************************************************************** assert response_body == {"details": "Task not found"} - + assert Task.query.get(1) is None # @pytest.mark.skip(reason="No way to test this feature yet") def test_delete_task(client, one_task): @@ -169,7 +169,7 @@ def test_delete_task_not_found(client): # **Complete test with assertion about response body*************** # ***************************************************************** assert response_body == {"details": "Task not found" } - + assert Task.query.get(1) == None # @pytest.mark.skip(reason="No way to test this feature yet") def test_create_task_must_contain_title(client): diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index 74a43e002..594df9ffd 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -97,7 +97,6 @@ def test_update_goal(client, one_goal): # assertion 1 goes here assert response.status_code == 200 # assertion 2 goes here - assert "goal" in response_body assert response_body == { "goal": { "id": 1,