From 50d14f0f3abdb097cb333431ecbe4ac51b1bc7d3 Mon Sep 17 00:00:00 2001 From: Jessica Date: Thu, 3 Nov 2022 10:49:25 -0700 Subject: [PATCH 01/18] Modified for wave 1 --- .gitignore | 5 +- app/__init__.py | 8 +++ app/models/task.py | 7 +- app/routes.py | 31 ++++++++- migrations/README | 1 + migrations/alembic.ini | 45 +++++++++++++ migrations/env.py | 96 ++++++++++++++++++++++++++++ migrations/script.py.mako | 24 +++++++ migrations/versions/0be59eb4c489_.py | 39 +++++++++++ migrations/versions/7ee9aabc2920_.py | 28 ++++++++ tests/test_wave_01.py | 2 +- 11 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/0be59eb4c489_.py create mode 100644 migrations/versions/7ee9aabc2920_.py diff --git a/.gitignore b/.gitignore index 4e9b18359..e0da1df2d 100644 --- a/.gitignore +++ b/.gitignore @@ -138,4 +138,7 @@ dmypy.json .pytype/ # Cython debug symbols -cython_debug/ \ No newline at end of file +cython_debug/ + +# files +REMEMEBER.txt \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py index 2764c4cc8..619345a78 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,5 +30,13 @@ def create_app(test_config=None): migrate.init_app(app, db) # Register Blueprints here + from app.models.task import Task + from app.routes import task_bp + app.register_blueprint(task_bp) return app + + + + + diff --git a/app/models/task.py b/app/models/task.py index c91ab281f..ac08cb65e 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -2,4 +2,9 @@ class Task(db.Model): - task_id = db.Column(db.Integer, primary_key=True) + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + title = db.Column(db.String) + description = db.Column(db.String) + completed_at = db.Column(db.DateTime, nullable=True) + is_complete = db.Column(db.Boolean, default=False) + diff --git a/app/routes.py b/app/routes.py index 3aae38d49..d3805743b 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1 +1,30 @@ -from flask import Blueprint \ No newline at end of file +from flask import Blueprint,jsonify, abort, make_response, request +from app import db +from app.models.task import Task + +task_bp = Blueprint("task_bp",__name__, url_prefix="/task") + + + +@task_bp.route("", methods=["POST"]) +def add_task(): + request_body = request.get_json() + + new_task = Task( + title = request_body["title"], + description = request_body["description"], + completed_at = request_body["completed_at"] + ) + + db.session.add(new_task) + + db.session.commit() + + return {"task":{ + "id":new_task.id, + "title":new_task.title, + "description":new_task.description, + "is_complete":new_task.is_complete + } + }, 201 + 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/0be59eb4c489_.py b/migrations/versions/0be59eb4c489_.py new file mode 100644 index 000000000..e51c8b181 --- /dev/null +++ b/migrations/versions/0be59eb4c489_.py @@ -0,0 +1,39 @@ +"""empty message + +Revision ID: 0be59eb4c489 +Revises: +Create Date: 2022-11-02 14:46:01.654728 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '0be59eb4c489' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('goal', + sa.Column('goal_id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('goal_id') + ) + op.create_table('task', + sa.Column('id', sa.Integer(), 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('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/7ee9aabc2920_.py b/migrations/versions/7ee9aabc2920_.py new file mode 100644 index 000000000..313d6fe3c --- /dev/null +++ b/migrations/versions/7ee9aabc2920_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 7ee9aabc2920 +Revises: 0be59eb4c489 +Create Date: 2022-11-02 16:24:50.717247 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7ee9aabc2920' +down_revision = '0be59eb4c489' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('is_complete', sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('task', 'is_complete') + # ### end Alembic commands ### diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index dca626d78..9dd8fe3e6 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") From a7173743c2d9d1dd01946aedce8d9b2406b89632 Mon Sep 17 00:00:00 2001 From: Jessica Date: Sat, 5 Nov 2022 11:53:10 -0700 Subject: [PATCH 02/18] Passed 5 tests --- app/routes.py | 67 ++++++++++++++++++++++++++++++++++++------- tests/test_wave_01.py | 16 ++++------- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/app/routes.py b/app/routes.py index d3805743b..21efcce30 100644 --- a/app/routes.py +++ b/app/routes.py @@ -2,8 +2,21 @@ from app import db from app.models.task import Task -task_bp = Blueprint("task_bp",__name__, url_prefix="/task") +task_bp = Blueprint("task_bp",__name__, url_prefix="/tasks") +def get_one_task_or_abort(task_id): + try: + task_id = int(task_id) + except ValueError: + response_str = f"Invalid task_id: `{task_id}`.Id must be an integer." + abort(make_response(jsonify({'message':response_str}), 400)) + + matching_task = Task.query.get(task_id) + + if not matching_task: + response_str = f"Task with id {task_id} was not found in the database." + abort(make_response(jsonify({'message':response_str}), 404)) + return matching_task @task_bp.route("", methods=["POST"]) @@ -12,19 +25,53 @@ def add_task(): new_task = Task( title = request_body["title"], - description = request_body["description"], - completed_at = request_body["completed_at"] - ) + description = request_body["description"] + ) db.session.add(new_task) db.session.commit() - return {"task":{ - "id":new_task.id, - "title":new_task.title, - "description":new_task.description, - "is_complete":new_task.is_complete - } + return { + "task":{ + "id" : new_task.id, + "title" : new_task.title, + "description" : new_task.description, + "is_complete" : new_task.is_complete + } }, 201 +@task_bp.route("", methods=["GET"]) +def get_all_tasks(): + + tasks_query = request.args.get("title") + if tasks_query is None: + tasks = Task.query.all() + + else: + tasks = Task.query.filter_by(title=tasks_query) + + response = [] + for task in tasks: + task_dict = { + "id":task.id, + "title":task.title, + "description":task.description, + "is_complete":task.is_complete + } + response.append(task_dict) + return jsonify(response), 200 + +@task_bp.route("/", methods=["GET"]) +def get_one_task(task_id): + chosen_task = get_one_task_or_abort(task_id) + task_dict = { + "task":{ + "id" : chosen_task.id, + "title" : chosen_task.title, + "description" : chosen_task.description, + "is_complete" : chosen_task.is_complete + } + } + + return jsonify(task_dict), 200 \ No newline at end of file diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index 9dd8fe3e6..ffc4f3e31 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -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") @@ -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") @@ -59,14 +59,10 @@ def test_get_task_not_found(client): # Assert assert response.status_code == 404 + assert "message" in response_body - raise Exception("Complete test with assertion about response body") - # ***************************************************************** - # **Complete test with assertion about 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_create_task(client): # Act response = client.post("/tasks", json={ @@ -93,7 +89,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 76f4558522a899ae6eb21568b5e2588d3123b9b0 Mon Sep 17 00:00:00 2001 From: Jessica Date: Sat, 5 Nov 2022 18:09:28 -0700 Subject: [PATCH 03/18] Passed 9 tests --- app/routes.py | 35 ++++++++++++++++++++++++++++++++++- tests/test_wave_01.py | 20 ++++++-------------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/app/routes.py b/app/routes.py index 21efcce30..a8bc48bd9 100644 --- a/app/routes.py +++ b/app/routes.py @@ -74,4 +74,37 @@ def get_one_task(task_id): } } - return jsonify(task_dict), 200 \ No newline at end of file + return jsonify(task_dict), 200 + +@task_bp.route("/", methods=["PUT"]) +def update_one_task(task_id): + chosen_task = get_one_task_or_abort(task_id) + request_body = request.get_json() + + if "title" not in request_body or \ + "description" not in request_body: + return jsonify({"message": "request must include name, description, type "}), 400 + + chosen_task.title = request_body["title"] + chosen_task.description = request_body["description"] + + db.session.commit() + + return { + "task":{ + "id" : chosen_task.id, + "title" : chosen_task.title, + "description" : chosen_task.description, + "is_complete" : chosen_task.is_complete + } + }, 200 + +@task_bp.route("/", methods=["DELETE"]) +def delete_one_task(task_id): + chosen_task = get_one_task_or_abort(task_id) + + db.session.delete(chosen_task) + + db.session.commit() + + return jsonify({"details": f'Task {task_id} "{chosen_task.title}" successfully deleted'}), 200 \ No newline at end of file diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index ffc4f3e31..6e784b447 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -115,7 +115,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={ @@ -126,14 +126,10 @@ def test_update_task_not_found(client): # Assert assert response.status_code == 404 - - raise Exception("Complete test with assertion about response body") - # ***************************************************************** - # **Complete test with assertion about response body*************** - # ***************************************************************** + assert "message" in 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_delete_task(client, one_task): # Act response = client.delete("/tasks/1") @@ -148,7 +144,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") @@ -157,15 +153,11 @@ def test_delete_task_not_found(client): # Assert assert response.status_code == 404 - raise Exception("Complete test with assertion about response body") - # ***************************************************************** - # **Complete test with assertion about response body*************** - # ***************************************************************** - + assert "message" in response_body assert Task.query.all() == [] -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_create_task_must_contain_title(client): # Act response = client.post("/tasks", json={ From f2afdd931fbbe78ef360adc8e09fc9d425213f44 Mon Sep 17 00:00:00 2001 From: Jessica Date: Sun, 6 Nov 2022 17:24:46 -0800 Subject: [PATCH 04/18] WAVE 1 was a little modified --- app/models/task.py | 8 ++++++++ app/routes.py | 31 +++++++++++++++++++------------ tests/test_wave_01.py | 2 +- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index ac08cb65e..a2509d932 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -8,3 +8,11 @@ class Task(db.Model): completed_at = db.Column(db.DateTime, nullable=True) is_complete = db.Column(db.Boolean, default=False) + def to_dict(self): # 1) Add no need to migrate and upgrade + task_dict = { + "id":self.id, + "title":self.title, + "description":self.description, + "is_complete": self.is_complete, + } + return task_dict \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index a8bc48bd9..e75f94bbf 100644 --- a/app/routes.py +++ b/app/routes.py @@ -23,10 +23,14 @@ def get_one_task_or_abort(task_id): def add_task(): request_body = request.get_json() + if "title" not in request_body or \ + "description" not in request_body: + return jsonify({"details": "Invalid data"}), 400 + new_task = Task( title = request_body["title"], - description = request_body["description"] - ) + description = request_body["description"], + ) db.session.add(new_task) @@ -51,17 +55,10 @@ def get_all_tasks(): else: tasks = Task.query.filter_by(title=tasks_query) - response = [] - for task in tasks: - task_dict = { - "id":task.id, - "title":task.title, - "description":task.description, - "is_complete":task.is_complete - } - response.append(task_dict) + response = [task.to_dict() for task in tasks] return jsonify(response), 200 + @task_bp.route("/", methods=["GET"]) def get_one_task(task_id): chosen_task = get_one_task_or_abort(task_id) @@ -107,4 +104,14 @@ def delete_one_task(task_id): db.session.commit() - return jsonify({"details": f'Task {task_id} "{chosen_task.title}" successfully deleted'}), 200 \ No newline at end of file + return jsonify({"details": f'Task {task_id} "{chosen_task.title}" successfully deleted'}), 200 + +@task_bp.route("", methods=["GET"]) +def get_tasks_sorted(task_id): + tasks_query = request.args.get("title") + + tasks = Task.query.order_by("title=tasks_query") + + response = [task.to_dict() for task in tasks] + return jsonify(response), 200 + diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index 6e784b447..f5bbd1bff 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -174,7 +174,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 382d76e5d2b21054dfa29e8ea5d392ec6175b6d1 Mon Sep 17 00:00:00 2001 From: Jessica Date: Mon, 7 Nov 2022 18:32:38 -0800 Subject: [PATCH 05/18] Modified wave 2 --- app/models/task.py | 2 +- app/routes.py | 29 +++++++++++++++-------------- tests/test_wave_02.py | 4 ++-- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index a2509d932..fb0c45701 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -8,7 +8,7 @@ class Task(db.Model): completed_at = db.Column(db.DateTime, nullable=True) is_complete = db.Column(db.Boolean, default=False) - def to_dict(self): # 1) Add no need to migrate and upgrade + def to_dict(self): task_dict = { "id":self.id, "title":self.title, diff --git a/app/routes.py b/app/routes.py index e75f94bbf..5c22590f2 100644 --- a/app/routes.py +++ b/app/routes.py @@ -29,8 +29,7 @@ def add_task(): new_task = Task( title = request_body["title"], - description = request_body["description"], - ) + description = request_body["description"]) db.session.add(new_task) @@ -47,13 +46,18 @@ def add_task(): @task_bp.route("", methods=["GET"]) def get_all_tasks(): - - tasks_query = request.args.get("title") - if tasks_query is None: + title_param = request.args.get("title") + + sort_param = request.args.get("sort") + + if not title_param: tasks = Task.query.all() + + if sort_param == "desc": + tasks = Task.query.order_by(Task.title.desc()).all() - else: - tasks = Task.query.filter_by(title=tasks_query) + if sort_param == "asc": + tasks = Task.query.order_by(Task.title.asc()).all() response = [task.to_dict() for task in tasks] return jsonify(response), 200 @@ -106,12 +110,9 @@ def delete_one_task(task_id): return jsonify({"details": f'Task {task_id} "{chosen_task.title}" successfully deleted'}), 200 -@task_bp.route("", methods=["GET"]) -def get_tasks_sorted(task_id): - tasks_query = request.args.get("title") - tasks = Task.query.order_by("title=tasks_query") - - response = [task.to_dict() for task in tasks] - return jsonify(response), 200 +@task_bp.route("/", methods=["PATCH", "PUT"]) +def update_is_complete(task_id): + pass + 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 5c9f0f647f52d36ae3a8eeed9ec0df4f115a23f6 Mon Sep 17 00:00:00 2001 From: Jessica Date: Mon, 7 Nov 2022 21:27:05 -0800 Subject: [PATCH 06/18] Modified wave 3 --- app/models/task.py | 2 +- app/routes.py | 16 ++++++++++++--- migrations/versions/17c4518a2cc0_.py | 28 ++++++++++++++++++++++++++ migrations/versions/20af7b695e68_.py | 30 ++++++++++++++++++++++++++++ migrations/versions/c11d68cafc2e_.py | 28 ++++++++++++++++++++++++++ migrations/versions/cd32ae4bc203_.py | 28 ++++++++++++++++++++++++++ migrations/versions/e9cd118730ab_.py | 30 ++++++++++++++++++++++++++++ migrations/versions/ef0ef800bc1d_.py | 28 ++++++++++++++++++++++++++ migrations/versions/f7651e896dd2_.py | 30 ++++++++++++++++++++++++++++ tests/test_wave_03.py | 2 +- 10 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 migrations/versions/17c4518a2cc0_.py create mode 100644 migrations/versions/20af7b695e68_.py create mode 100644 migrations/versions/c11d68cafc2e_.py create mode 100644 migrations/versions/cd32ae4bc203_.py create mode 100644 migrations/versions/e9cd118730ab_.py create mode 100644 migrations/versions/ef0ef800bc1d_.py create mode 100644 migrations/versions/f7651e896dd2_.py diff --git a/app/models/task.py b/app/models/task.py index fb0c45701..80dcdf314 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -13,6 +13,6 @@ def to_dict(self): "id":self.id, "title":self.title, "description":self.description, - "is_complete": self.is_complete, + "is_complete": True if self.completed_at else False } return task_dict \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 5c22590f2..7d8c14d30 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,6 +1,7 @@ from flask import Blueprint,jsonify, abort, make_response, request from app import db from app.models.task import Task +from datetime import date task_bp = Blueprint("task_bp",__name__, url_prefix="/tasks") @@ -111,8 +112,17 @@ def delete_one_task(task_id): return jsonify({"details": f'Task {task_id} "{chosen_task.title}" successfully deleted'}), 200 -@task_bp.route("/", methods=["PATCH", "PUT"]) +@task_bp.route("//mark_complete", methods=["PATCH"]) def update_is_complete(task_id): - pass - + update_is_valid_id = get_one_task_or_abort(task_id) + + update_is_valid_id.complete_at = date.today() + db.session.commit() + + return jsonify({"task":update_is_valid_id.to_dict()}), 200 + +# @task_bp.route("//mark_incomplete", methods=["PATCH"]) +# def update_is_complete(task_id): + +# return jsonify({"details": f'Task {task_id} "{chosen_task.title}" successfully deleted'}), 200 \ No newline at end of file diff --git a/migrations/versions/17c4518a2cc0_.py b/migrations/versions/17c4518a2cc0_.py new file mode 100644 index 000000000..38230c779 --- /dev/null +++ b/migrations/versions/17c4518a2cc0_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 17c4518a2cc0 +Revises: c11d68cafc2e +Create Date: 2022-11-07 21:13:40.932202 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '17c4518a2cc0' +down_revision = 'c11d68cafc2e' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('is_complete', sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('task', 'is_complete') + # ### end Alembic commands ### diff --git a/migrations/versions/20af7b695e68_.py b/migrations/versions/20af7b695e68_.py new file mode 100644 index 000000000..6ce619ce8 --- /dev/null +++ b/migrations/versions/20af7b695e68_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 20af7b695e68 +Revises: ef0ef800bc1d +Create Date: 2022-11-07 20:29:05.134359 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '20af7b695e68' +down_revision = 'ef0ef800bc1d' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('completed_at', sa.DateTime(), nullable=True)) + op.drop_column('task', 'is_complete') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('is_complete', sa.BOOLEAN(), autoincrement=False, nullable=True)) + op.drop_column('task', 'completed_at') + # ### end Alembic commands ### diff --git a/migrations/versions/c11d68cafc2e_.py b/migrations/versions/c11d68cafc2e_.py new file mode 100644 index 000000000..4abf93660 --- /dev/null +++ b/migrations/versions/c11d68cafc2e_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: c11d68cafc2e +Revises: e9cd118730ab +Create Date: 2022-11-07 21:13:01.164462 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c11d68cafc2e' +down_revision = 'e9cd118730ab' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('task', 'is_complete') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('is_complete', sa.BOOLEAN(), autoincrement=False, nullable=True)) + # ### end Alembic commands ### diff --git a/migrations/versions/cd32ae4bc203_.py b/migrations/versions/cd32ae4bc203_.py new file mode 100644 index 000000000..ce3ad1eee --- /dev/null +++ b/migrations/versions/cd32ae4bc203_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: cd32ae4bc203 +Revises: 20af7b695e68 +Create Date: 2022-11-07 20:29:47.481458 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'cd32ae4bc203' +down_revision = '20af7b695e68' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('is_complete', sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('task', 'is_complete') + # ### end Alembic commands ### diff --git a/migrations/versions/e9cd118730ab_.py b/migrations/versions/e9cd118730ab_.py new file mode 100644 index 000000000..89517d8a9 --- /dev/null +++ b/migrations/versions/e9cd118730ab_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: e9cd118730ab +Revises: f7651e896dd2 +Create Date: 2022-11-07 21:12:18.617815 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'e9cd118730ab' +down_revision = 'f7651e896dd2' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('completed_at', sa.DateTime(), nullable=True)) + op.drop_column('task', 'complete_at') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('complete_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) + op.drop_column('task', 'completed_at') + # ### end Alembic commands ### diff --git a/migrations/versions/ef0ef800bc1d_.py b/migrations/versions/ef0ef800bc1d_.py new file mode 100644 index 000000000..391c8a387 --- /dev/null +++ b/migrations/versions/ef0ef800bc1d_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: ef0ef800bc1d +Revises: 7ee9aabc2920 +Create Date: 2022-11-07 20:13:41.672169 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'ef0ef800bc1d' +down_revision = '7ee9aabc2920' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('task', 'completed_at') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('completed_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) + # ### end Alembic commands ### diff --git a/migrations/versions/f7651e896dd2_.py b/migrations/versions/f7651e896dd2_.py new file mode 100644 index 000000000..1232778a5 --- /dev/null +++ b/migrations/versions/f7651e896dd2_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: f7651e896dd2 +Revises: cd32ae4bc203 +Create Date: 2022-11-07 21:09:08.064488 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'f7651e896dd2' +down_revision = 'cd32ae4bc203' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('complete_at', sa.DateTime(), nullable=True)) + op.drop_column('task', 'completed_at') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('completed_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) + op.drop_column('task', 'complete_at') + # ### end Alembic commands ### diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index 32d379822..05a5dc7a6 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 d1328272518c403ac7c6ef464cedd9ac94188677 Mon Sep 17 00:00:00 2001 From: Jessica Date: Tue, 8 Nov 2022 13:14:43 -0800 Subject: [PATCH 07/18] Modified wave 3 --- app/models/task.py | 20 ++++++++++++++------ app/routes.py | 7 ++++--- tests/test_wave_03.py | 2 +- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 80dcdf314..2832fd230 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -9,10 +9,18 @@ class Task(db.Model): is_complete = db.Column(db.Boolean, default=False) def to_dict(self): - task_dict = { - "id":self.id, - "title":self.title, - "description":self.description, - "is_complete": True if self.completed_at else False - } + if self.completed_at is None: + task_dict = { + "id":self.id, + "title":self.title, + "description":self.description, + "is_complete": False + } + else: + task_dict = { + "id":self.id, + "title":self.title, + "description":self.description, + "is_complete": True + } return task_dict \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 7d8c14d30..6b1ce802b 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,7 +1,7 @@ from flask import Blueprint,jsonify, abort, make_response, request from app import db from app.models.task import Task -from datetime import date +from datetime import datetime task_bp = Blueprint("task_bp",__name__, url_prefix="/tasks") @@ -116,11 +116,12 @@ def delete_one_task(task_id): def update_is_complete(task_id): update_is_valid_id = get_one_task_or_abort(task_id) - update_is_valid_id.complete_at = date.today() + update_is_valid_id.completed_at = datetime.today() + db.session.add(update_is_valid_id) db.session.commit() - return jsonify({"task":update_is_valid_id.to_dict()}), 200 + return jsonify({"task":Task.to_dict(update_is_valid_id)}), 200 # @task_bp.route("//mark_incomplete", methods=["PATCH"]) # def update_is_complete(task_id): diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index 05a5dc7a6..d485a0b2f 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") From c83d4fe0025adf9c4ae7b35d283c7f74e004c3c2 Mon Sep 17 00:00:00 2001 From: Jessica Date: Tue, 8 Nov 2022 13:39:50 -0800 Subject: [PATCH 08/18] Finished wave 3 --- app/routes.py | 13 +++++++++---- tests/test_wave_03.py | 19 ++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/app/routes.py b/app/routes.py index 6b1ce802b..c1521b736 100644 --- a/app/routes.py +++ b/app/routes.py @@ -123,7 +123,12 @@ def update_is_complete(task_id): return jsonify({"task":Task.to_dict(update_is_valid_id)}), 200 -# @task_bp.route("//mark_incomplete", methods=["PATCH"]) -# def update_is_complete(task_id): - -# return jsonify({"details": f'Task {task_id} "{chosen_task.title}" successfully deleted'}), 200 \ No newline at end of file +@task_bp.route("//mark_incomplete", methods=["PATCH"]) +def update_incompete(task_id): + task_incompete = get_one_task_or_abort(task_id) + task_incompete.completed_at = None + + db.session.add(task_incompete) + db.session.commit() + + return jsonify({"task":Task.to_dict(task_incompete)}), 200 \ No newline at end of file diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index d485a0b2f..05f4a9f8e 100644 --- a/tests/test_wave_03.py +++ b/tests/test_wave_03.py @@ -62,7 +62,7 @@ def test_mark_incomplete_on_complete_task(client, completed_task): assert Task.query.get(1).completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_on_completed_task(client, completed_task): # Arrange """ @@ -99,7 +99,7 @@ def test_mark_complete_on_completed_task(client, completed_task): assert Task.query.get(1).completed_at -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_incomplete_on_incomplete_task(client, one_task): # Act response = client.patch("/tasks/1/mark_incomplete") @@ -119,7 +119,7 @@ def test_mark_incomplete_on_incomplete_task(client, one_task): assert Task.query.get(1).completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_missing_task(client): # Act response = client.patch("/tasks/1/mark_complete") @@ -127,14 +127,11 @@ def test_mark_complete_missing_task(client): # Assert assert response.status_code == 404 + assert "message" in response_body == {'message': 'Task with id 1 was not found in the database.'} - raise Exception("Complete test with assertion about response body") - # ***************************************************************** - # **Complete test with assertion about 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_mark_incomplete_missing_task(client): # Act response = client.patch("/tasks/1/mark_incomplete") @@ -142,8 +139,4 @@ def test_mark_incomplete_missing_task(client): # Assert assert response.status_code == 404 - - raise Exception("Complete test with assertion about response body") - # ***************************************************************** - # **Complete test with assertion about response body*************** - # ***************************************************************** + assert "message" in response_body == {'message': 'Task with id 1 was not found in the database.'} From 2dcdcbc28e9a3f176083312d60a750515fc90a79 Mon Sep 17 00:00:00 2001 From: Jessica Date: Wed, 9 Nov 2022 14:48:29 -0800 Subject: [PATCH 09/18] Modified some of wave 5 --- app/__init__.py | 5 +- app/models/goal.py | 8 ++ app/routes.py | 154 ++++++++++++++++++++++++++- migrations/versions/345ce3a3ef6e_.py | 28 +++++ tests/test_wave_05.py | 48 +++++---- 5 files changed, 217 insertions(+), 26 deletions(-) create mode 100644 migrations/versions/345ce3a3ef6e_.py diff --git a/app/__init__.py b/app/__init__.py index 619345a78..59946d165 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,10 +30,11 @@ def create_app(test_config=None): migrate.init_app(app, db) # Register Blueprints here - from app.models.task import Task + # from app.models.task import Task from app.routes import task_bp app.register_blueprint(task_bp) - + from app.routes import goal_bp + app.register_blueprint(goal_bp) return app diff --git a/app/models/goal.py b/app/models/goal.py index b0ed11dd8..3059a8c41 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) + + def to_dict(self): + goal_dict = { + "id":self.goal_id, + "title":self.title + } + return goal_dict \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index c1521b736..721e6eab7 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,7 +1,10 @@ from flask import Blueprint,jsonify, abort, make_response, request from app import db from app.models.task import Task +from app.models.goal import Goal from datetime import datetime +import os +import requests task_bp = Blueprint("task_bp",__name__, url_prefix="/tasks") @@ -85,7 +88,7 @@ def update_one_task(task_id): if "title" not in request_body or \ "description" not in request_body: - return jsonify({"message": "request must include name, description, type "}), 400 + return jsonify({"message": "Request must include title and description."}), 400 chosen_task.title = request_body["title"] chosen_task.description = request_body["description"] @@ -120,7 +123,20 @@ def update_is_complete(task_id): db.session.add(update_is_valid_id) db.session.commit() - + SLACK_PATH = "https://slack.com/api/chat.postMessage" + SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN") + + headers = { + "Authorization" : f"Bearer {SLACK_BOT_TOKEN}" + } + params = { + "channel": "task-notifications", + "text": f"Someone just completed the task {update_is_valid_id.title}" + } + + requests.get(url=SLACK_PATH, headers=headers, params=params) + + return jsonify({"task":Task.to_dict(update_is_valid_id)}), 200 @task_bp.route("//mark_incomplete", methods=["PATCH"]) @@ -131,4 +147,136 @@ def update_incompete(task_id): db.session.add(task_incompete) db.session.commit() - return jsonify({"task":Task.to_dict(task_incompete)}), 200 \ No newline at end of file + return jsonify({"task":Task.to_dict(task_incompete)}), 200 + + +# ################################################################### +goal_bp = Blueprint("goal_bp",__name__, url_prefix="/goals") + +def get_one_goal_or_abort(goal_id): + try: + goal_id = int(goal_id) + except ValueError: + response_str = f"Invalid goal_id: `{goal_id}`.Id must be an integer." + abort(make_response(jsonify({'message':response_str}), 400)) + + matching_goal = Goal.query.get(goal_id) + + if not matching_goal: + response_str = f"Goal with id {goal_id} was not found in the database." + abort(make_response(jsonify({'message':response_str}), 404)) + return matching_goal + +@goal_bp.route("", methods=["POST"]) +def add_goal(): + request_body = request.get_json() + + if "title" not in request_body: + return jsonify({"details": "Invalid data"}), 400 + + new_goal = Goal( + title = request_body["title"]) + + db.session.add(new_goal) + + db.session.commit() + + return { + "goal":{ + "id" : new_goal.goal_id, + "title" : new_goal.title + } + }, 201 + + +@goal_bp.route("", methods=["GET"]) +def get_all_goals(): + + goals = Goal.query.all() + response = [] + for goal in goals: + goal_dict = { + "id": goal.goal_id, + "title": goal.title + } + response.append(goal_dict) + + # response = [Goal.to_dict(goal) for goal in goals] + return jsonify(response), 200 + + +@goal_bp.route("/", methods=["GET"]) +def get_one_goal(goal_id): + chosen_goal = get_one_goal_or_abort(goal_id) + goal_dict = { + "goal":{ + "id" : chosen_goal.goal_id, + "title" : chosen_goal.title + } + } + + return jsonify(goal_dict), 200 + +@goal_bp.route("/", methods=["PUT"]) +def update_one_task(goal_id): + chosen_goal = get_one_goal_or_abort(goal_id) + request_body = request.get_json() + + if "title" not in request_body: + return jsonify({"message": "request must include title."}), 400 + + chosen_goal.title = request_body["title"] + + db.session.commit() + + return { + "goal":{ + "id" : chosen_goal.goal_id, + "title" : chosen_goal.title + } + }, 200 + +# @task_bp.route("/", methods=["DELETE"]) +# def delete_one_task(task_id): +# chosen_task = get_one_task_or_abort(task_id) + +# db.session.delete(chosen_task) + +# db.session.commit() + +# return jsonify({"details": f'Task {task_id} "{chosen_task.title}" successfully deleted'}), 200 + + +# @task_bp.route("//mark_complete", methods=["PATCH"]) +# def update_is_complete(task_id): +# update_is_valid_id = get_one_task_or_abort(task_id) + +# update_is_valid_id.completed_at = datetime.today() +# db.session.add(update_is_valid_id) +# db.session.commit() + +# SLACK_PATH = "https://slack.com/api/chat.postMessage" +# SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN") + +# headers = { +# "Authorization" : f"Bearer {SLACK_BOT_TOKEN}" +# } +# params = { +# "channel": "task-notifications", +# "text": f"Someone just completed the task {update_is_valid_id.title}" +# } + +# requests.get(url=SLACK_PATH, headers=headers, params=params) + + +# return jsonify({"task":Task.to_dict(update_is_valid_id)}), 200 + +# @task_bp.route("//mark_incomplete", methods=["PATCH"]) +# def update_incompete(task_id): +# task_incompete = get_one_task_or_abort(task_id) +# task_incompete.completed_at = None + +# db.session.add(task_incompete) +# db.session.commit() + +# return jsonify({"task":Task.to_dict(task_incompete)}), 200 \ No newline at end of file diff --git a/migrations/versions/345ce3a3ef6e_.py b/migrations/versions/345ce3a3ef6e_.py new file mode 100644 index 000000000..1108e640a --- /dev/null +++ b/migrations/versions/345ce3a3ef6e_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 345ce3a3ef6e +Revises: 17c4518a2cc0 +Create Date: 2022-11-09 13:09:49.973267 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '345ce3a3ef6e' +down_revision = '17c4518a2cc0' +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 aee7c52a1..01510bded 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -1,7 +1,8 @@ +from app.models.goal import Goal import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_goals_no_saved_goals(client): # Act response = client.get("/goals") @@ -12,7 +13,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") @@ -28,8 +29,7 @@ def test_get_goals_one_saved_goal(client, one_goal): } ] - -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_goal(client, one_goal): # Act response = client.get("/goals/1") @@ -46,22 +46,19 @@ 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") - # Assert - # ---- Complete Test ---- - # assertion 1 goes here - # assertion 2 goes here - # ---- Complete Test ---- + # raise Exception("Complete test") + # Assert + assert response.status_code == 404 + assert "message" in 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_create_goal(client): # Act response = client.post("/goals", json={ @@ -80,18 +77,27 @@ 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") # Act - # ---- Complete Act Here ---- + response = client.put("/goals/1", json={ + "title": "Updated Goal title" + }) + response_body = response.get_json() # Assert - # ---- Complete Assertions Here ---- - # assertion 1 goes here - # assertion 2 goes here - # assertion 3 goes here - # ---- Complete Assertions Here ---- + assert response.status_code == 200 + assert "goal" in response_body + assert response_body == { + "goal": { + "id": 1, + "title": "Updated Goal title" + } + } + task = Goal.query.get(1) + assert task.title == 'Updated Goal title' + + @pytest.mark.skip(reason="test to be completed by student") From f6a7c36f1f945f35f7b5b49132fd343fa0c87860 Mon Sep 17 00:00:00 2001 From: Jessica Date: Wed, 9 Nov 2022 15:22:30 -0800 Subject: [PATCH 10/18] Finished wave 5 --- app/routes.py | 12 +++++------ tests/test_wave_05.py | 49 +++++++++++++++++++++---------------------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/app/routes.py b/app/routes.py index 721e6eab7..a479583b3 100644 --- a/app/routes.py +++ b/app/routes.py @@ -236,15 +236,15 @@ def update_one_task(goal_id): } }, 200 -# @task_bp.route("/", methods=["DELETE"]) -# def delete_one_task(task_id): -# chosen_task = get_one_task_or_abort(task_id) +@goal_bp.route("/", methods=["DELETE"]) +def delete_one_goal(goal_id): + chosen_goal = get_one_goal_or_abort(goal_id) -# db.session.delete(chosen_task) + db.session.delete(chosen_goal) -# db.session.commit() + db.session.commit() -# return jsonify({"details": f'Task {task_id} "{chosen_task.title}" successfully deleted'}), 200 + return jsonify({"details": f'Goal {goal_id} "{chosen_goal.title}" successfully deleted'}), 200 # @task_bp.route("//mark_complete", methods=["PATCH"]) diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index 01510bded..f26a930c7 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -94,26 +94,27 @@ def test_update_goal(client, one_goal): "title": "Updated Goal title" } } - task = Goal.query.get(1) - assert task.title == 'Updated Goal title' + goal = Goal.query.get(1) + assert goal.title == 'Updated Goal title' -@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") - # Act - # ---- Complete Act Here ---- + + response = client.get("/goals/1", json={ + "title": "Updated title of the goal. " + }) + response_body = response.get_json() # Assert - # ---- Complete Assertions Here ---- - # assertion 1 goes here - # assertion 2 goes here - # ---- Complete Assertions Here ---- + assert response.status_code == 404 + assert "message" in 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_delete_goal(client, one_goal): # Act response = client.delete("/goals/1") @@ -129,28 +130,26 @@ def test_delete_goal(client, one_goal): # Check that the goal was deleted response = client.get("/goals/1") assert response.status_code == 404 - - raise Exception("Complete test with assertion about response body") - # ***************************************************************** - # **Complete test with assertion about response body*************** - # ***************************************************************** + response_body = response.get_json() + assert "message" in response_body -@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") - # Act - # ---- Complete Act Here ---- + response = client.delete("/goals/1") + response_body = response.get_json() # Assert - # ---- Complete Assertions Here ---- - # assertion 1 goes here - # assertion 2 goes here - # ---- Complete Assertions Here ---- + assert response.status_code == 404 + assert "message" in response_body + assert response_body == { + "message": "Goal with id 1 was not found in the database." + } + assert Goal.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_create_goal_missing_title(client): # Act response = client.post("/goals", json={}) From 281cab7d66953bdd94cb032eef2110894a933878 Mon Sep 17 00:00:00 2001 From: Jessica Date: Wed, 9 Nov 2022 18:01:14 -0800 Subject: [PATCH 11/18] Passed all the Tests for wave 5 --- app/models/goal.py | 1 + app/routes.py | 34 ---------------------------------- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/app/models/goal.py b/app/models/goal.py index 3059a8c41..ec9c918f0 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -5,6 +5,7 @@ class Goal(db.Model): goal_id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) + def to_dict(self): goal_dict = { "id":self.goal_id, diff --git a/app/routes.py b/app/routes.py index a479583b3..efb541658 100644 --- a/app/routes.py +++ b/app/routes.py @@ -246,37 +246,3 @@ def delete_one_goal(goal_id): return jsonify({"details": f'Goal {goal_id} "{chosen_goal.title}" successfully deleted'}), 200 - -# @task_bp.route("//mark_complete", methods=["PATCH"]) -# def update_is_complete(task_id): -# update_is_valid_id = get_one_task_or_abort(task_id) - -# update_is_valid_id.completed_at = datetime.today() -# db.session.add(update_is_valid_id) -# db.session.commit() - -# SLACK_PATH = "https://slack.com/api/chat.postMessage" -# SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN") - -# headers = { -# "Authorization" : f"Bearer {SLACK_BOT_TOKEN}" -# } -# params = { -# "channel": "task-notifications", -# "text": f"Someone just completed the task {update_is_valid_id.title}" -# } - -# requests.get(url=SLACK_PATH, headers=headers, params=params) - - -# return jsonify({"task":Task.to_dict(update_is_valid_id)}), 200 - -# @task_bp.route("//mark_incomplete", methods=["PATCH"]) -# def update_incompete(task_id): -# task_incompete = get_one_task_or_abort(task_id) -# task_incompete.completed_at = None - -# db.session.add(task_incompete) -# db.session.commit() - -# return jsonify({"task":Task.to_dict(task_incompete)}), 200 \ No newline at end of file From ae1193d11a92950a46803e56b9d6427eca0bd379 Mon Sep 17 00:00:00 2001 From: Jessica Date: Wed, 9 Nov 2022 19:13:52 -0800 Subject: [PATCH 12/18] Factored code --- app/routes.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/routes.py b/app/routes.py index efb541658..abc7e8a84 100644 --- a/app/routes.py +++ b/app/routes.py @@ -193,15 +193,7 @@ def add_goal(): def get_all_goals(): goals = Goal.query.all() - response = [] - for goal in goals: - goal_dict = { - "id": goal.goal_id, - "title": goal.title - } - response.append(goal_dict) - - # response = [Goal.to_dict(goal) for goal in goals] + response = [Goal.to_dict(goal) for goal in goals] return jsonify(response), 200 From 1969afeb4710916638d4a2a45fdce1e527751890 Mon Sep 17 00:00:00 2001 From: Jessica Date: Wed, 9 Nov 2022 19:22:20 -0800 Subject: [PATCH 13/18] Refactored --- app/models/goal.py | 5 ++-- app/models/task.py | 1 + app/routes.py | 37 ++++++++++++++-------------- migrations/versions/cf32372db3e4_.py | 30 ++++++++++++++++++++++ 4 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 migrations/versions/cf32372db3e4_.py diff --git a/app/models/goal.py b/app/models/goal.py index ec9c918f0..8f1016680 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -2,13 +2,14 @@ class Goal(db.Model): - goal_id = db.Column(db.Integer, primary_key=True) + id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) + # task = db.relationship('Task',backref='Goal') def to_dict(self): goal_dict = { - "id":self.goal_id, + "id":self.id, "title":self.title } return goal_dict \ No newline at end of file diff --git a/app/models/task.py b/app/models/task.py index 2832fd230..159053842 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -7,6 +7,7 @@ class Task(db.Model): description = db.Column(db.String) completed_at = db.Column(db.DateTime, nullable=True) is_complete = db.Column(db.Boolean, default=False) + # goal_id = db.Column(db.Integer,db.ForeignKey('goal.id')) def to_dict(self): if self.completed_at is None: diff --git a/app/routes.py b/app/routes.py index abc7e8a84..70b0b116b 100644 --- a/app/routes.py +++ b/app/routes.py @@ -153,17 +153,17 @@ def update_incompete(task_id): # ################################################################### goal_bp = Blueprint("goal_bp",__name__, url_prefix="/goals") -def get_one_goal_or_abort(goal_id): +def get_one_goal_or_abort(id): try: - goal_id = int(goal_id) + id = int(id) except ValueError: - response_str = f"Invalid goal_id: `{goal_id}`.Id must be an integer." + response_str = f"Invalid id: `{id}`.Id must be an integer." abort(make_response(jsonify({'message':response_str}), 400)) - matching_goal = Goal.query.get(goal_id) + matching_goal = Goal.query.get(id) if not matching_goal: - response_str = f"Goal with id {goal_id} was not found in the database." + response_str = f"Goal with id {id} was not found in the database." abort(make_response(jsonify({'message':response_str}), 404)) return matching_goal @@ -183,7 +183,7 @@ def add_goal(): return { "goal":{ - "id" : new_goal.goal_id, + "id" : new_goal.id, "title" : new_goal.title } }, 201 @@ -193,25 +193,26 @@ def add_goal(): def get_all_goals(): goals = Goal.query.all() + response = [Goal.to_dict(goal) for goal in goals] return jsonify(response), 200 -@goal_bp.route("/", methods=["GET"]) -def get_one_goal(goal_id): - chosen_goal = get_one_goal_or_abort(goal_id) +@goal_bp.route("/", methods=["GET"]) +def get_one_goal(id): + chosen_goal = get_one_goal_or_abort(id) goal_dict = { "goal":{ - "id" : chosen_goal.goal_id, + "id" : chosen_goal.id, "title" : chosen_goal.title } } return jsonify(goal_dict), 200 -@goal_bp.route("/", methods=["PUT"]) -def update_one_task(goal_id): - chosen_goal = get_one_goal_or_abort(goal_id) +@goal_bp.route("/", methods=["PUT"]) +def update_one_task(id): + chosen_goal = get_one_goal_or_abort(id) request_body = request.get_json() if "title" not in request_body: @@ -223,18 +224,18 @@ def update_one_task(goal_id): return { "goal":{ - "id" : chosen_goal.goal_id, + "id" : chosen_goal.id, "title" : chosen_goal.title } }, 200 -@goal_bp.route("/", methods=["DELETE"]) -def delete_one_goal(goal_id): - chosen_goal = get_one_goal_or_abort(goal_id) +@goal_bp.route("/", methods=["DELETE"]) +def delete_one_goal(id): + chosen_goal = get_one_goal_or_abort(id) db.session.delete(chosen_goal) db.session.commit() - return jsonify({"details": f'Goal {goal_id} "{chosen_goal.title}" successfully deleted'}), 200 + return jsonify({"details": f'Goal {id} "{chosen_goal.title}" successfully deleted'}), 200 diff --git a/migrations/versions/cf32372db3e4_.py b/migrations/versions/cf32372db3e4_.py new file mode 100644 index 000000000..132c19e3a --- /dev/null +++ b/migrations/versions/cf32372db3e4_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: cf32372db3e4 +Revises: 345ce3a3ef6e +Create Date: 2022-11-09 19:21:20.493716 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'cf32372db3e4' +down_revision = '345ce3a3ef6e' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('goal', sa.Column('id', sa.Integer(), nullable=False)) + op.drop_column('goal', 'goal_id') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('goal', sa.Column('goal_id', sa.INTEGER(), autoincrement=True, nullable=False)) + op.drop_column('goal', 'id') + # ### end Alembic commands ### From cb273e5078fd845aa502dfe4655fa03ac27c0d06 Mon Sep 17 00:00:00 2001 From: Jessica Date: Wed, 9 Nov 2022 19:30:17 -0800 Subject: [PATCH 14/18] Modified the classes --- app/models/goal.py | 2 +- app/models/task.py | 2 +- migrations/versions/418acff5aaa2_.py | 30 ++++++++++++++++++++++++++++ tests/test_wave_06.py | 2 +- 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 migrations/versions/418acff5aaa2_.py diff --git a/app/models/goal.py b/app/models/goal.py index 8f1016680..74e6a4848 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -4,7 +4,7 @@ class Goal(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) - # task = db.relationship('Task',backref='Goal') + task = db.relationship('Task', backref='Goal', lazy=True) def to_dict(self): diff --git a/app/models/task.py b/app/models/task.py index 159053842..16237e84d 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -7,7 +7,7 @@ class Task(db.Model): description = db.Column(db.String) completed_at = db.Column(db.DateTime, nullable=True) is_complete = db.Column(db.Boolean, default=False) - # goal_id = db.Column(db.Integer,db.ForeignKey('goal.id')) + goal_id = db.Column(db.Integer, db.ForeignKey('goal.id')) def to_dict(self): if self.completed_at is None: diff --git a/migrations/versions/418acff5aaa2_.py b/migrations/versions/418acff5aaa2_.py new file mode 100644 index 000000000..2effc0efa --- /dev/null +++ b/migrations/versions/418acff5aaa2_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 418acff5aaa2 +Revises: cf32372db3e4 +Create Date: 2022-11-09 19:29:11.116983 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '418acff5aaa2' +down_revision = 'cf32372db3e4' +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'], ['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_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 db6f6e58d934867d00b5bb97ce5e643be236dcb6 Mon Sep 17 00:00:00 2001 From: Jessica Date: Thu, 10 Nov 2022 17:16:59 -0800 Subject: [PATCH 15/18] Drop all databases because error occured --- app/__init__.py | 3 ++ app/models/goal.py | 6 +-- app/models/task.py | 3 +- app/routes.py | 48 +++++++++++++++---- .../{0be59eb4c489_.py => 02075f0c1a4d_.py} | 14 ++++-- migrations/versions/17c4518a2cc0_.py | 28 ----------- migrations/versions/20af7b695e68_.py | 30 ------------ migrations/versions/345ce3a3ef6e_.py | 28 ----------- migrations/versions/418acff5aaa2_.py | 30 ------------ migrations/versions/7ee9aabc2920_.py | 28 ----------- migrations/versions/c11d68cafc2e_.py | 28 ----------- migrations/versions/cd32ae4bc203_.py | 28 ----------- migrations/versions/cf32372db3e4_.py | 30 ------------ migrations/versions/e9cd118730ab_.py | 30 ------------ migrations/versions/ef0ef800bc1d_.py | 28 ----------- migrations/versions/f7651e896dd2_.py | 30 ------------ tests/test_wave_06.py | 2 +- 17 files changed, 56 insertions(+), 338 deletions(-) rename migrations/versions/{0be59eb4c489_.py => 02075f0c1a4d_.py} (62%) delete mode 100644 migrations/versions/17c4518a2cc0_.py delete mode 100644 migrations/versions/20af7b695e68_.py delete mode 100644 migrations/versions/345ce3a3ef6e_.py delete mode 100644 migrations/versions/418acff5aaa2_.py delete mode 100644 migrations/versions/7ee9aabc2920_.py delete mode 100644 migrations/versions/c11d68cafc2e_.py delete mode 100644 migrations/versions/cd32ae4bc203_.py delete mode 100644 migrations/versions/cf32372db3e4_.py delete mode 100644 migrations/versions/e9cd118730ab_.py delete mode 100644 migrations/versions/ef0ef800bc1d_.py delete mode 100644 migrations/versions/f7651e896dd2_.py diff --git a/app/__init__.py b/app/__init__.py index 59946d165..a08292638 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -33,8 +33,11 @@ def create_app(test_config=None): # from app.models.task import Task from app.routes import task_bp app.register_blueprint(task_bp) + from app.routes import goal_bp app.register_blueprint(goal_bp) + + return app diff --git a/app/models/goal.py b/app/models/goal.py index 74e6a4848..5223d97f4 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -2,14 +2,14 @@ class Goal(db.Model): - id = db.Column(db.Integer, primary_key=True) + goal_id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String) - task = db.relationship('Task', backref='Goal', lazy=True) + tasks = db.relationship('Task', back_populates='goals', lazy=True) def to_dict(self): goal_dict = { - "id":self.id, + "id":self.goal_id, "title":self.title } return goal_dict \ No newline at end of file diff --git a/app/models/task.py b/app/models/task.py index 16237e84d..0f0cdaeb8 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -7,7 +7,8 @@ class Task(db.Model): description = db.Column(db.String) completed_at = db.Column(db.DateTime, nullable=True) is_complete = db.Column(db.Boolean, default=False) - goal_id = db.Column(db.Integer, db.ForeignKey('goal.id')) + goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id')) + goals = db.relationship('Goal', back_populates='tasks') def to_dict(self): if self.completed_at is None: diff --git a/app/routes.py b/app/routes.py index 70b0b116b..21f0e2197 100644 --- a/app/routes.py +++ b/app/routes.py @@ -183,7 +183,7 @@ def add_goal(): return { "goal":{ - "id" : new_goal.id, + "id" : new_goal.goal_id, "title" : new_goal.title } }, 201 @@ -203,16 +203,16 @@ def get_one_goal(id): chosen_goal = get_one_goal_or_abort(id) goal_dict = { "goal":{ - "id" : chosen_goal.id, + "id" : chosen_goal.goal_id, "title" : chosen_goal.title } } return jsonify(goal_dict), 200 -@goal_bp.route("/", methods=["PUT"]) -def update_one_task(id): - chosen_goal = get_one_goal_or_abort(id) +@goal_bp.route("/", methods=["PUT"]) +def update_one_task(goal_id): + chosen_goal = get_one_goal_or_abort(goal_id) request_body = request.get_json() if "title" not in request_body: @@ -224,18 +224,46 @@ def update_one_task(id): return { "goal":{ - "id" : chosen_goal.id, + "id" : chosen_goal.goal_id, "title" : chosen_goal.title } }, 200 -@goal_bp.route("/", methods=["DELETE"]) -def delete_one_goal(id): - chosen_goal = get_one_goal_or_abort(id) +@goal_bp.route("/", methods=["DELETE"]) +def delete_one_goal(goal_id): + chosen_goal = get_one_goal_or_abort(goal_id) db.session.delete(chosen_goal) db.session.commit() - return jsonify({"details": f'Goal {id} "{chosen_goal.title}" successfully deleted'}), 200 + return jsonify({"details": f'Goal {goal_id} "{chosen_goal.title}" successfully deleted'}), 200 + +# ################################################################### + +# POST +# @goal_bp.route("//tasks", methods=["POST"]) +# def post_task_ids_to_goal(goal_id): +# # tasks is a list +# # check if a goal +# # check if a task +# # do request + +# request_body = request.get_json() + +# new_goal_with_task = { +# "task_ids": +# } + + + +# db.session.add(new_goal_with_task) + +# db.session.commit() + +# return { +# "id" : new_goal_with_task.goal_id, +# "task_ids" : new_goal_with_task. # ???????? +# }, 201 +# # GET \ No newline at end of file diff --git a/migrations/versions/0be59eb4c489_.py b/migrations/versions/02075f0c1a4d_.py similarity index 62% rename from migrations/versions/0be59eb4c489_.py rename to migrations/versions/02075f0c1a4d_.py index e51c8b181..de8dfdc33 100644 --- a/migrations/versions/0be59eb4c489_.py +++ b/migrations/versions/02075f0c1a4d_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 0be59eb4c489 +Revision ID: 02075f0c1a4d Revises: -Create Date: 2022-11-02 14:46:01.654728 +Create Date: 2022-11-10 17:01:54.500414 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '0be59eb4c489' +revision = '02075f0c1a4d' down_revision = None branch_labels = None depends_on = None @@ -19,14 +19,18 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.create_table('goal', - sa.Column('goal_id', sa.Integer(), nullable=False), + sa.Column('goal_id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.String(), nullable=True), sa.PrimaryKeyConstraint('goal_id') ) op.create_table('task', - sa.Column('id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), autoincrement=True, 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.Column('is_complete', sa.Boolean(), nullable=True), + sa.Column('goal_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['goal_id'], ['goal.goal_id'], ), sa.PrimaryKeyConstraint('id') ) # ### end Alembic commands ### diff --git a/migrations/versions/17c4518a2cc0_.py b/migrations/versions/17c4518a2cc0_.py deleted file mode 100644 index 38230c779..000000000 --- a/migrations/versions/17c4518a2cc0_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: 17c4518a2cc0 -Revises: c11d68cafc2e -Create Date: 2022-11-07 21:13:40.932202 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '17c4518a2cc0' -down_revision = 'c11d68cafc2e' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('is_complete', sa.Boolean(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('task', 'is_complete') - # ### end Alembic commands ### diff --git a/migrations/versions/20af7b695e68_.py b/migrations/versions/20af7b695e68_.py deleted file mode 100644 index 6ce619ce8..000000000 --- a/migrations/versions/20af7b695e68_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""empty message - -Revision ID: 20af7b695e68 -Revises: ef0ef800bc1d -Create Date: 2022-11-07 20:29:05.134359 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '20af7b695e68' -down_revision = 'ef0ef800bc1d' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('completed_at', sa.DateTime(), nullable=True)) - op.drop_column('task', 'is_complete') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('is_complete', sa.BOOLEAN(), autoincrement=False, nullable=True)) - op.drop_column('task', 'completed_at') - # ### end Alembic commands ### diff --git a/migrations/versions/345ce3a3ef6e_.py b/migrations/versions/345ce3a3ef6e_.py deleted file mode 100644 index 1108e640a..000000000 --- a/migrations/versions/345ce3a3ef6e_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: 345ce3a3ef6e -Revises: 17c4518a2cc0 -Create Date: 2022-11-09 13:09:49.973267 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '345ce3a3ef6e' -down_revision = '17c4518a2cc0' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('goal', sa.Column('title', sa.String(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('goal', 'title') - # ### end Alembic commands ### diff --git a/migrations/versions/418acff5aaa2_.py b/migrations/versions/418acff5aaa2_.py deleted file mode 100644 index 2effc0efa..000000000 --- a/migrations/versions/418acff5aaa2_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""empty message - -Revision ID: 418acff5aaa2 -Revises: cf32372db3e4 -Create Date: 2022-11-09 19:29:11.116983 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '418acff5aaa2' -down_revision = 'cf32372db3e4' -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'], ['id']) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'task', type_='foreignkey') - op.drop_column('task', 'goal_id') - # ### end Alembic commands ### diff --git a/migrations/versions/7ee9aabc2920_.py b/migrations/versions/7ee9aabc2920_.py deleted file mode 100644 index 313d6fe3c..000000000 --- a/migrations/versions/7ee9aabc2920_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: 7ee9aabc2920 -Revises: 0be59eb4c489 -Create Date: 2022-11-02 16:24:50.717247 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '7ee9aabc2920' -down_revision = '0be59eb4c489' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('is_complete', sa.Boolean(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('task', 'is_complete') - # ### end Alembic commands ### diff --git a/migrations/versions/c11d68cafc2e_.py b/migrations/versions/c11d68cafc2e_.py deleted file mode 100644 index 4abf93660..000000000 --- a/migrations/versions/c11d68cafc2e_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: c11d68cafc2e -Revises: e9cd118730ab -Create Date: 2022-11-07 21:13:01.164462 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'c11d68cafc2e' -down_revision = 'e9cd118730ab' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('task', 'is_complete') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('is_complete', sa.BOOLEAN(), autoincrement=False, nullable=True)) - # ### end Alembic commands ### diff --git a/migrations/versions/cd32ae4bc203_.py b/migrations/versions/cd32ae4bc203_.py deleted file mode 100644 index ce3ad1eee..000000000 --- a/migrations/versions/cd32ae4bc203_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: cd32ae4bc203 -Revises: 20af7b695e68 -Create Date: 2022-11-07 20:29:47.481458 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'cd32ae4bc203' -down_revision = '20af7b695e68' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('is_complete', sa.Boolean(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('task', 'is_complete') - # ### end Alembic commands ### diff --git a/migrations/versions/cf32372db3e4_.py b/migrations/versions/cf32372db3e4_.py deleted file mode 100644 index 132c19e3a..000000000 --- a/migrations/versions/cf32372db3e4_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""empty message - -Revision ID: cf32372db3e4 -Revises: 345ce3a3ef6e -Create Date: 2022-11-09 19:21:20.493716 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'cf32372db3e4' -down_revision = '345ce3a3ef6e' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('goal', sa.Column('id', sa.Integer(), nullable=False)) - op.drop_column('goal', 'goal_id') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('goal', sa.Column('goal_id', sa.INTEGER(), autoincrement=True, nullable=False)) - op.drop_column('goal', 'id') - # ### end Alembic commands ### diff --git a/migrations/versions/e9cd118730ab_.py b/migrations/versions/e9cd118730ab_.py deleted file mode 100644 index 89517d8a9..000000000 --- a/migrations/versions/e9cd118730ab_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""empty message - -Revision ID: e9cd118730ab -Revises: f7651e896dd2 -Create Date: 2022-11-07 21:12:18.617815 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = 'e9cd118730ab' -down_revision = 'f7651e896dd2' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('completed_at', sa.DateTime(), nullable=True)) - op.drop_column('task', 'complete_at') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('complete_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) - op.drop_column('task', 'completed_at') - # ### end Alembic commands ### diff --git a/migrations/versions/ef0ef800bc1d_.py b/migrations/versions/ef0ef800bc1d_.py deleted file mode 100644 index 391c8a387..000000000 --- a/migrations/versions/ef0ef800bc1d_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: ef0ef800bc1d -Revises: 7ee9aabc2920 -Create Date: 2022-11-07 20:13:41.672169 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = 'ef0ef800bc1d' -down_revision = '7ee9aabc2920' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('task', 'completed_at') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('completed_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) - # ### end Alembic commands ### diff --git a/migrations/versions/f7651e896dd2_.py b/migrations/versions/f7651e896dd2_.py deleted file mode 100644 index 1232778a5..000000000 --- a/migrations/versions/f7651e896dd2_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""empty message - -Revision ID: f7651e896dd2 -Revises: cd32ae4bc203 -Create Date: 2022-11-07 21:09:08.064488 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = 'f7651e896dd2' -down_revision = 'cd32ae4bc203' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('complete_at', sa.DateTime(), nullable=True)) - op.drop_column('task', 'completed_at') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('completed_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) - op.drop_column('task', 'complete_at') - # ### end Alembic commands ### diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index 566c52a39..8afa4325e 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 3c7a72dabf6a54df8aa62ea08ade58b95b693fdd Mon Sep 17 00:00:00 2001 From: Jessica Date: Thu, 10 Nov 2022 17:29:28 -0800 Subject: [PATCH 16/18] Modified task class --- app/models/task.py | 2 +- app/routes.py | 36 ++++++++++++++++++------------------ tests/test_wave_06.py | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 0f0cdaeb8..0c7170805 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -7,7 +7,7 @@ class Task(db.Model): description = db.Column(db.String) completed_at = db.Column(db.DateTime, nullable=True) is_complete = db.Column(db.Boolean, default=False) - goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id')) + goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'), nullable = True ) goals = db.relationship('Goal', back_populates='tasks') def to_dict(self): diff --git a/app/routes.py b/app/routes.py index 21f0e2197..cc6f3eecd 100644 --- a/app/routes.py +++ b/app/routes.py @@ -242,28 +242,28 @@ def delete_one_goal(goal_id): # ################################################################### # POST -# @goal_bp.route("//tasks", methods=["POST"]) -# def post_task_ids_to_goal(goal_id): -# # tasks is a list -# # check if a goal -# # check if a task -# # do request - -# request_body = request.get_json() - -# new_goal_with_task = { -# "task_ids": -# } +@goal_bp.route("//tasks", methods=["POST"]) +def post_task_ids_to_goal(goal_id): + # tasks is a list + # check if a goal + # check if a task + # do request + + request_body = request.get_json() + + new_goal_with_task = { + "task_ids": Goal.tasks + } -# db.session.add(new_goal_with_task) + db.session.add(new_goal_with_task) -# db.session.commit() + db.session.commit() -# return { -# "id" : new_goal_with_task.goal_id, -# "task_ids" : new_goal_with_task. # ???????? -# }, 201 + return { + "id" : new_goal_with_task.goal_id, + "task_ids" : new_goal_with_task. # ???????? + }, 201 # # GET \ 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 07f9391d589fc002c4a8062b5f54272e64a57ddf Mon Sep 17 00:00:00 2001 From: Jessica Date: Thu, 10 Nov 2022 18:24:12 -0800 Subject: [PATCH 17/18] Completed wave 6 --- app/models/task.py | 21 ++++++++++++----- app/routes.py | 54 ++++++++++++++++++++++++------------------- tests/test_wave_06.py | 17 +++++--------- 3 files changed, 51 insertions(+), 41 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 0c7170805..25bb4f086 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -12,12 +12,21 @@ class Task(db.Model): def to_dict(self): if self.completed_at is None: - task_dict = { - "id":self.id, - "title":self.title, - "description":self.description, - "is_complete": False - } + if self.goal_id is None: + task_dict = { + "id":self.id, + "title":self.title, + "description":self.description, + "is_complete": False + } + else: + task_dict = { + "id":self.id, + "goal_id": self.goal_id, + "title":self.title, + "description":self.description, + "is_complete": False + } else: task_dict = { "id":self.id, diff --git a/app/routes.py b/app/routes.py index cc6f3eecd..3c18e232a 100644 --- a/app/routes.py +++ b/app/routes.py @@ -69,17 +69,8 @@ def get_all_tasks(): @task_bp.route("/", methods=["GET"]) def get_one_task(task_id): - chosen_task = get_one_task_or_abort(task_id) - task_dict = { - "task":{ - "id" : chosen_task.id, - "title" : chosen_task.title, - "description" : chosen_task.description, - "is_complete" : chosen_task.is_complete - } - } - - return jsonify(task_dict), 200 + chosen_task = get_one_task_or_abort(task_id) + return jsonify({"task":Task.to_dict(chosen_task)}), 200 @task_bp.route("/", methods=["PUT"]) def update_one_task(task_id): @@ -244,26 +235,41 @@ def delete_one_goal(goal_id): # POST @goal_bp.route("//tasks", methods=["POST"]) def post_task_ids_to_goal(goal_id): - # tasks is a list - # check if a goal - # check if a task - # do request + + big_goal_id = get_one_goal_or_abort(goal_id) request_body = request.get_json() + for task_id in request_body["task_ids"]: + valid_task = get_one_task_or_abort(task_id) + valid_task.goals = big_goal_id + db.session.commit() + + big_goal_task_id_list = [] + for task in big_goal_id.tasks: + big_goal_task_id_list.append(task.id) + new_goal_with_task = { - "task_ids": Goal.tasks + "id" : big_goal_id.goal_id, + "task_ids": big_goal_task_id_list } - - db.session.add(new_goal_with_task) + return jsonify(new_goal_with_task), 200 - db.session.commit() +# # GET +@goal_bp.route("//tasks", methods=["GET"]) +def get_one_goal_with_tasks(goal_id): + chosen_big_goal_id = get_one_goal_or_abort(goal_id) - return { - "id" : new_goal_with_task.goal_id, - "task_ids" : new_goal_with_task. # ???????? - }, 201 + big_goal_task_id_list = [] + for task in chosen_big_goal_id.tasks: + big_goal_task_id_list.append(Task.to_dict(task)) -# # GET \ No newline at end of file + goal_dict = { + "id" : chosen_big_goal_id.goal_id, + "title" : chosen_big_goal_id.title, + "tasks": big_goal_task_id_list + } + + return jsonify(goal_dict), 200 \ No newline at end of file diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index 566c52a39..94a5575a8 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") @@ -50,14 +50,9 @@ def test_get_tasks_for_specific_goal_no_goal(client): # Assert assert response.status_code == 404 + assert "message" in response_body - raise Exception("Complete test with assertion about response body") - # ***************************************************************** - # **Complete test with assertion about 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_for_specific_goal_no_tasks(client, one_goal): # Act response = client.get("/goals/1/tasks") @@ -74,7 +69,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") @@ -99,7 +94,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 02355f998548575abedd2042de8da33e3b3527df Mon Sep 17 00:00:00 2001 From: Jessica Date: Thu, 10 Nov 2022 21:40:28 -0800 Subject: [PATCH 18/18] Updated the requirements for deployment. --- Procfile | 1 + requirements.txt | 2 ++ 2 files changed, 3 insertions(+) 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 diff --git a/requirements.txt b/requirements.txt index cacdbc36e..db54fe283 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ blinker==1.4 certifi==2020.12.5 chardet==4.0.0 click==7.1.2 +coverage==6.5.0 Flask==1.1.2 Flask-Migrate==2.6.0 Flask-SQLAlchemy==2.4.4 @@ -30,5 +31,6 @@ requests==2.25.1 six==1.15.0 SQLAlchemy==1.3.23 toml==0.10.2 +tomli==2.0.1 urllib3==1.26.5 Werkzeug==1.0.1