From d9059f323adcd15d45385a01dd510662d7737027 Mon Sep 17 00:00:00 2001 From: Whitney Date: Wed, 10 May 2023 11:46:22 -0700 Subject: [PATCH 01/20] All tests passing Wave 1 but last 2 --- app/__init__.py | 4 +- app/models/task.py | 39 ++++- app/routes.py | 160 +++++++++++++++++- migrations/README | 1 + migrations/alembic.ini | 45 +++++ migrations/env.py | 96 +++++++++++ migrations/script.py.mako | 24 +++ ...a91689b1b85e_recreating_databases_again.py | 39 +++++ tests/test_wave_01.py | 34 ++-- 9 files changed, 425 insertions(+), 17 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/a91689b1b85e_recreating_databases_again.py diff --git a/app/__init__.py b/app/__init__.py index 2764c4cc8..90f13d662 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,5 +30,7 @@ def create_app(test_config=None): migrate.init_app(app, db) # Register Blueprints here - + from .routes import tasks_bp + app.register_blueprint(tasks_bp) + return app diff --git a/app/models/task.py b/app/models/task.py index c91ab281f..191963889 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -2,4 +2,41 @@ 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, nullable=False) + description = db.Column(db.String, nullable=False) + completed_at = db.Column(db.DateTime, default = None) + # completed_at = db.Column(db.DateTime(timezone=True), nullable=True) + + + @classmethod + def from_dict(cls, task_data): + new_task = cls(title=task_data["title"], + description=task_data["description"], + completed_at=None) + # new_task = Task(title=task_data["title"], + # description=task_data["description"], + # completed_at=task_data["is_complete"]) + return new_task + + def to_dict(self): + return dict( + id = self.id, + title = self.title, + description = self.description, + is_complete = self.is_task_complete() + ) + # task_dict = {} + # task_dict["id"] = self.id + # task_dict["title"] = self.title + # task_dict["description"] = self.description + # task_dict["is complete"] = self.is_task_complete() + + # return task_dict + + def is_task_complete(self): + if self.completed_at == None: + self.completed_at = False + else: + self.completed_at = True + return self.completed_at diff --git a/app/routes.py b/app/routes.py index 3aae38d49..8292229d9 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1 +1,159 @@ -from flask import Blueprint \ No newline at end of file +from flask import Blueprint, jsonify, abort, make_response, request +from app.models.task import Task +from app import db + +tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") + +#POST /tasks ******************************* +@tasks_bp.route("", methods=["POST"]) +def create_task(): + try: + request_body = request.get_json() + new_task = Task.from_dict(request_body) + except: + return jsonify({"details": "Invalid data"}, 400) + + db.session.add(new_task) + db.session.commit() + + return jsonify({"task": new_task.to_dict()}), 201 + + # except: + # return jsonify({"details": "Invalid data"}, 400) + + +# new_task = get_task_instance(request) + +# db.session.add(new_task) +# db.session.commit() + +# task = new_task.to_json() + + # except: + # return jsonify({"details": "Invalid data"}, 400) + + # db.session.add(new_task) + # db.session.commit() + + # return make_response(jsonify({"task": new_task})), 201 + + +#Validate Model +def validate_model(cls, model_id): + try: + model_id = int(model_id) + except: + abort(make_response({"details": "Invalid data"}, 400)) + + task = cls.query.get(model_id) + + if not task: + message = {"details": "Invalid data"} + abort(make_response(message, 404)) + + return task + + +#GET /tasks +@tasks_bp.route("", methods=["GET"]) +def get_all_tasks(): + + title_query = request.args.get("title") + + + if title_query: + tasks = Task.query.filter_by(title=title_query) + else: + tasks = Task.query.all() + + tasks_response = [] + for task in tasks: + tasks_response.append(task.to_dict()) + + return jsonify(tasks_response), 200 + +# @tasks_bp.route("", methods=["GET"]) +# def get_all_tasks(): + +# title_query = request.args.get("title") + +# if title_query: +# tasks = Task.query.filter_by(title=title_query) +# else: +# tasks = Task.query.all() + +# tasks_response = [] +# for task in tasks: +# tasks_response.append(task.to_dict()) + +# return jsonify(tasks_response), 200 + + +#GET /tasks/ +@tasks_bp.route("/", methods=["GET"]) +def get_one_task(model_id): + task = validate_model(Task, model_id) + if task: + return {"task": task.to_dict()}, 200 + + else: + + return {'details': 'Invalid data'}, 404 + +#********************************************* +#PUT /tasks/ +@tasks_bp.route("", methods=["PUT"]) +def update_task(model_id): + try: + task = validate_model(Task, model_id) + except: + return jsonify({"Message": "Invalid id"}), 404 + + request_body = request.get_json() + + task.title = request_body["title"] + task.description = request_body["description"] + + db.session.commit() + + return jsonify({"task": task.to_dict()}), 200 + + # return make_response(jsonify({"task": task})), 200 +#***************************************** + +#DELETE /tasks/ +@tasks_bp.route("", methods=["DELETE"]) +def delete_task(model_id): + task = validate_model(Task, model_id) + + if task is None: + return {'details': 'Invalid data'}, 404 + # return jsonify(message="Task not found"), 404 + + db.session.delete(task) + db.session.commit() + + message = {"details": f"Task 1 \"{task.title}\" successfully deleted"} + return make_response(message, 200) + # message = {"details": f"Task 1 \"{task.title} 🏞\" successfully deleted"} + # return make_response(jsonify(message, 200)) + + + + + + + + + + +# @tasks_bp.route("", methods=['POST']) +# def create_task(): +# new_task = get_task_instance(request) + +# db.session.add(new_task) +# db.session.commit() + +# task = new_task.to_json() + +# return make_response(jsonify(task=task)), 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/a91689b1b85e_recreating_databases_again.py b/migrations/versions/a91689b1b85e_recreating_databases_again.py new file mode 100644 index 000000000..573cf3255 --- /dev/null +++ b/migrations/versions/a91689b1b85e_recreating_databases_again.py @@ -0,0 +1,39 @@ +"""recreating databases AGAIN + +Revision ID: a91689b1b85e +Revises: +Create Date: 2023-05-09 22:27:41.009158 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a91689b1b85e' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('goal', + sa.Column('goal_id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('goal_id') + ) + op.create_table('task', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.String(), nullable=False), + sa.Column('description', sa.String(), nullable=False), + 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/tests/test_wave_01.py b/tests/test_wave_01.py index dca626d78..18e1aff9c 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -2,7 +2,10 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +# print(f"{response = }") + # print(f"{response_body = }") + +# @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 +16,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 +35,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 +54,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 +62,15 @@ def test_get_task_not_found(client): # Assert assert response.status_code == 404 + assert response_body == {'details': 'Invalid data'} - 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*************** # ***************************************************************** -@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 +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={ @@ -119,7 +123,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={ @@ -130,14 +134,15 @@ def test_update_task_not_found(client): # Assert assert response.status_code == 404 + assert response_body == {"Message": "Invalid id"}, 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*************** # ***************************************************************** -@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") @@ -152,7 +157,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") @@ -160,8 +165,9 @@ def test_delete_task_not_found(client): # Assert assert response.status_code == 404 + assert response_body == {'details': 'Invalid data'} - 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*************** # ***************************************************************** @@ -169,7 +175,7 @@ def test_delete_task_not_found(client): assert Task.query.all() == [] -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_create_task_must_contain_title(client): # Act response = client.post("/tasks", json={ @@ -186,7 +192,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 ab6d797190099b47d3d6f4178df551c9886fb465 Mon Sep 17 00:00:00 2001 From: Whitney Date: Wed, 10 May 2023 13:12:06 -0700 Subject: [PATCH 02/20] WAVE 1 PASSING --- app/models/task.py | 10 -------- app/routes.py | 64 +++------------------------------------------- 2 files changed, 3 insertions(+), 71 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index 191963889..c775de50f 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -14,9 +14,6 @@ def from_dict(cls, task_data): new_task = cls(title=task_data["title"], description=task_data["description"], completed_at=None) - # new_task = Task(title=task_data["title"], - # description=task_data["description"], - # completed_at=task_data["is_complete"]) return new_task def to_dict(self): @@ -26,13 +23,6 @@ def to_dict(self): description = self.description, is_complete = self.is_task_complete() ) - # task_dict = {} - # task_dict["id"] = self.id - # task_dict["title"] = self.title - # task_dict["description"] = self.description - # task_dict["is complete"] = self.is_task_complete() - - # return task_dict def is_task_complete(self): if self.completed_at == None: diff --git a/app/routes.py b/app/routes.py index 8292229d9..8b201cfdc 100644 --- a/app/routes.py +++ b/app/routes.py @@ -4,39 +4,20 @@ tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") -#POST /tasks ******************************* +#POST /tasks @tasks_bp.route("", methods=["POST"]) def create_task(): try: request_body = request.get_json() new_task = Task.from_dict(request_body) except: - return jsonify({"details": "Invalid data"}, 400) + return make_response({"details": "Invalid data"}, 400) db.session.add(new_task) db.session.commit() return jsonify({"task": new_task.to_dict()}), 201 - # except: - # return jsonify({"details": "Invalid data"}, 400) - - -# new_task = get_task_instance(request) - -# db.session.add(new_task) -# db.session.commit() - -# task = new_task.to_json() - - # except: - # return jsonify({"details": "Invalid data"}, 400) - - # db.session.add(new_task) - # db.session.commit() - - # return make_response(jsonify({"task": new_task})), 201 - #Validate Model def validate_model(cls, model_id): @@ -72,21 +53,6 @@ def get_all_tasks(): return jsonify(tasks_response), 200 -# @tasks_bp.route("", methods=["GET"]) -# def get_all_tasks(): - -# title_query = request.args.get("title") - -# if title_query: -# tasks = Task.query.filter_by(title=title_query) -# else: -# tasks = Task.query.all() - -# tasks_response = [] -# for task in tasks: -# tasks_response.append(task.to_dict()) - -# return jsonify(tasks_response), 200 #GET /tasks/ @@ -99,8 +65,8 @@ def get_one_task(model_id): else: return {'details': 'Invalid data'}, 404 + -#********************************************* #PUT /tasks/ @tasks_bp.route("", methods=["PUT"]) def update_task(model_id): @@ -118,8 +84,6 @@ def update_task(model_id): return jsonify({"task": task.to_dict()}), 200 - # return make_response(jsonify({"task": task})), 200 -#***************************************** #DELETE /tasks/ @tasks_bp.route("", methods=["DELETE"]) @@ -135,25 +99,3 @@ def delete_task(model_id): message = {"details": f"Task 1 \"{task.title}\" successfully deleted"} return make_response(message, 200) - # message = {"details": f"Task 1 \"{task.title} 🏞\" successfully deleted"} - # return make_response(jsonify(message, 200)) - - - - - - - - - - -# @tasks_bp.route("", methods=['POST']) -# def create_task(): -# new_task = get_task_instance(request) - -# db.session.add(new_task) -# db.session.commit() - -# task = new_task.to_json() - -# return make_response(jsonify(task=task)), 201 From a6b708b047162ccc490002f9703c9987f4824366 Mon Sep 17 00:00:00 2001 From: Whitney Date: Wed, 10 May 2023 18:05:37 -0700 Subject: [PATCH 03/20] Wave 2 Passing All Tests --- app/routes.py | 40 ++++++++++++++++++++++++++++++++++------ tests/test_wave_02.py | 4 ++-- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/app/routes.py b/app/routes.py index 8b201cfdc..13d614aaa 100644 --- a/app/routes.py +++ b/app/routes.py @@ -39,13 +39,16 @@ def validate_model(cls, model_id): @tasks_bp.route("", methods=["GET"]) def get_all_tasks(): - title_query = request.args.get("title") + sort_query = request.args.get("sort") + if sort_query == "asc": + tasks = Task.query.order_by(Task.title.asc()).all() + + elif sort_query == "desc": + tasks = Task.query.order_by(Task.title.desc()).all() - if title_query: - tasks = Task.query.filter_by(title=title_query) - else: - tasks = Task.query.all() + # else: + # tasks = Task.query.all() tasks_response = [] for task in tasks: @@ -55,6 +58,25 @@ def get_all_tasks(): + +# @tasks_bp.route("", methods=["GET"]) +# def get_all_tasks(): + +# title_query = request.args.get("title") + +# if title_query: +# tasks = Task.query.filter_by(title=title_query) +# else: +# tasks = Task.query.all() + +# tasks_response = [] +# for task in tasks: +# tasks_response.append(task.to_dict()) + +# return jsonify(tasks_response), 200 + + + #GET /tasks/ @tasks_bp.route("/", methods=["GET"]) def get_one_task(model_id): @@ -63,7 +85,6 @@ def get_one_task(model_id): return {"task": task.to_dict()}, 200 else: - return {'details': 'Invalid data'}, 404 @@ -99,3 +120,10 @@ def delete_task(model_id): message = {"details": f"Task 1 \"{task.title}\" successfully deleted"} return make_response(message, 200) + + +#--------------------------------------------- + +@tasks_bp.route("/tasks?sort=asc", methods={"GET"}) +def get_sorted_array(): + task = validate_model(Task, model_id) 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 a79365741e078d3788afb08ecadb07177cdb0868 Mon Sep 17 00:00:00 2001 From: Whitney Date: Wed, 10 May 2023 21:38:12 -0700 Subject: [PATCH 04/20] Wave 3 Complete all Tests Passing --- app/models/task.py | 6 ++- app/routes.py | 91 ++++++++++++++++++++++++++++++------------- tests/test_wave_03.py | 18 +++++---- 3 files changed, 79 insertions(+), 36 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index c775de50f..a7164370e 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -1,12 +1,14 @@ from app import db +import datetime + class Task(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String, nullable=False) description = db.Column(db.String, nullable=False) - completed_at = db.Column(db.DateTime, default = None) - # completed_at = db.Column(db.DateTime(timezone=True), nullable=True) + # completed_at = db.Column(db.DateTime, default = None) + completed_at = db.Column(db.DateTime(timezone=True), nullable=True) @classmethod diff --git a/app/routes.py b/app/routes.py index 13d614aaa..83585595e 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,5 +1,6 @@ from flask import Blueprint, jsonify, abort, make_response, request from app.models.task import Task +from datetime import datetime from app import db tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") @@ -10,7 +11,7 @@ def create_task(): try: request_body = request.get_json() new_task = Task.from_dict(request_body) - except: + except KeyError as err: return make_response({"details": "Invalid data"}, 400) db.session.add(new_task) @@ -47,8 +48,8 @@ def get_all_tasks(): elif sort_query == "desc": tasks = Task.query.order_by(Task.title.desc()).all() - # else: - # tasks = Task.query.all() + else: + tasks = Task.query.all() tasks_response = [] for task in tasks: @@ -57,26 +58,6 @@ def get_all_tasks(): return jsonify(tasks_response), 200 - - -# @tasks_bp.route("", methods=["GET"]) -# def get_all_tasks(): - -# title_query = request.args.get("title") - -# if title_query: -# tasks = Task.query.filter_by(title=title_query) -# else: -# tasks = Task.query.all() - -# tasks_response = [] -# for task in tasks: -# tasks_response.append(task.to_dict()) - -# return jsonify(tasks_response), 200 - - - #GET /tasks/ @tasks_bp.route("/", methods=["GET"]) def get_one_task(model_id): @@ -124,6 +105,64 @@ def delete_task(model_id): #--------------------------------------------- -@tasks_bp.route("/tasks?sort=asc", methods={"GET"}) -def get_sorted_array(): - task = validate_model(Task, model_id) + +@tasks_bp.route("/mark_complete", methods=["PATCH"]) +def mark_task_complete(model_id): + try: + new_task = validate_model(Task, model_id) + except: + return jsonify({"Message": "Invalid id"}), 404 + + # if new_task.completed_at == None: + new_task.completed_at = datetime.utcnow() + + # task.completed_at = request_body["completed_at"] + + # new_task.completed_at = request_body["completed_at"] + db.session.commit() + + return jsonify({"task": new_task.to_dict()}), 200 + + +@tasks_bp.route("/mark_incomplete", methods=["PATCH"]) +def mark_task_incomplete(model_id): + try: + new_task = validate_model(Task, model_id) + except: + return jsonify({"Message": "Invalid id"}), 404 + + new_task.completed_at = None + + db.session.commit() + + return jsonify({"task": new_task.to_dict()}), 200 + + +#def completed_task(app): + # new_task = Task( + # title="Go on my daily walk 🏞", description="Notice something new every day", completed_at=datetime.utcnow()) + # db.session.add(new_task) + # db.session.commit() + + + + + +# @tasks_bp.route("/mark_complete", methods=["PATCH"]) +# def mark_task_complete(model_id): +# try: +# task = validate_model(Task, model_id) +# except: +# return jsonify({"Message": "Invalid id"}), 404 + +# request_body = request.get_json() + +# # task.completed_at = request_body["completed_at"] + +# # if task.completed_at == None: +# # task.completed_at = datetime.now() + + +# 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 32d379822..47162f496 100644 --- a/tests/test_wave_03.py +++ b/tests/test_wave_03.py @@ -5,7 +5,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_on_incomplete_task(client, one_task): # Arrange """ @@ -42,7 +42,7 @@ def test_mark_complete_on_incomplete_task(client, one_task): assert Task.query.get(1).completed_at -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_incomplete_on_complete_task(client, completed_task): # Act response = client.patch("/tasks/1/mark_incomplete") @@ -62,7 +62,7 @@ def test_mark_incomplete_on_complete_task(client, completed_task): assert Task.query.get(1).completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_on_completed_task(client, completed_task): # Arrange """ @@ -99,7 +99,7 @@ def test_mark_complete_on_completed_task(client, completed_task): assert Task.query.get(1).completed_at -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_incomplete_on_incomplete_task(client, one_task): # Act response = client.patch("/tasks/1/mark_incomplete") @@ -119,7 +119,7 @@ def test_mark_incomplete_on_incomplete_task(client, one_task): assert Task.query.get(1).completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_missing_task(client): # Act response = client.patch("/tasks/1/mark_complete") @@ -127,14 +127,15 @@ def test_mark_complete_missing_task(client): # Assert assert response.status_code == 404 + assert response_body == {'Message': 'Invalid id'} - 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*************** # ***************************************************************** -@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 +143,9 @@ def test_mark_incomplete_missing_task(client): # Assert assert response.status_code == 404 + response_body == {'Message': 'Invalid id'} - raise Exception("Complete test with assertion about response body") + # raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** From ac87e15b6a302416f31704a628decb082ddd4bee Mon Sep 17 00:00:00 2001 From: Whitney Date: Thu, 11 May 2023 11:39:10 -0700 Subject: [PATCH 05/20] Wave 4 function written stillfigureing out oAuth key placement --- app/routes.py | 73 ++++++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/app/routes.py b/app/routes.py index 83585595e..fe5636a64 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,4 +1,5 @@ from flask import Blueprint, jsonify, abort, make_response, request +import requests from app.models.task import Task from datetime import datetime from app import db @@ -43,10 +44,10 @@ def get_all_tasks(): sort_query = request.args.get("sort") if sort_query == "asc": - tasks = Task.query.order_by(Task.title.asc()).all() + tasks = Task.query.order_by(Task.title) elif sort_query == "desc": - tasks = Task.query.order_by(Task.title.desc()).all() + tasks = Task.query.order_by(Task.title.desc()) else: tasks = Task.query.all() @@ -94,7 +95,6 @@ def delete_task(model_id): if task is None: return {'details': 'Invalid data'}, 404 - # return jsonify(message="Task not found"), 404 db.session.delete(task) db.session.commit() @@ -105,64 +105,59 @@ def delete_task(model_id): #--------------------------------------------- - @tasks_bp.route("/mark_complete", methods=["PATCH"]) def mark_task_complete(model_id): try: new_task = validate_model(Task, model_id) except: return jsonify({"Message": "Invalid id"}), 404 - - # if new_task.completed_at == None: - new_task.completed_at = datetime.utcnow() - - # task.completed_at = request_body["completed_at"] - - # new_task.completed_at = request_body["completed_at"] - db.session.commit() + + # Send the message to Slack + message = f"Someone just completed the task {new_task.title} My Beautiful Task" + payload = { + "text": message, + "channel": "api-test-channel", + "token": "your_slack_token" + } + response = requests.post("https://slack.com/api/chat.postMessage", data=payload) return jsonify({"task": new_task.to_dict()}), 200 - - -@tasks_bp.route("/mark_incomplete", methods=["PATCH"]) -def mark_task_incomplete(model_id): - try: - new_task = validate_model(Task, model_id) - except: - return jsonify({"Message": "Invalid id"}), 404 - - new_task.completed_at = None - db.session.commit() + # if: + # https://slack.com/api/chat.postMessage + # return "Someone just completed the task {task.title} My Beautiful Task" - return jsonify({"task": new_task.to_dict()}), 200 + # new_task.completed_at = datetime.utcnow() -#def completed_task(app): - # new_task = Task( - # title="Go on my daily walk 🏞", description="Notice something new every day", completed_at=datetime.utcnow()) - # db.session.add(new_task) # db.session.commit() - - - + # return jsonify({"task": new_task.to_dict()}), 200 # @tasks_bp.route("/mark_complete", methods=["PATCH"]) # def mark_task_complete(model_id): # try: -# task = validate_model(Task, model_id) +# new_task = validate_model(Task, model_id) # except: # return jsonify({"Message": "Invalid id"}), 404 - -# request_body = request.get_json() -# # task.completed_at = request_body["completed_at"] +# new_task.completed_at = datetime.utcnow() -# # if task.completed_at == None: -# # task.completed_at = datetime.now() +# db.session.commit() +# return jsonify({"task": new_task.to_dict()}), 200 -# db.session.commit() -# return jsonify({"task": task.to_dict()}), 200 +@tasks_bp.route("/mark_incomplete", methods=["PATCH"]) +def mark_task_incomplete(model_id): + try: + new_task = validate_model(Task, model_id) + except: + return jsonify({"Message": "Invalid id"}), 404 + + new_task.completed_at = None + + db.session.commit() + + return jsonify({"task": new_task.to_dict()}), 200 + From f2b3a26c97e4cc1858f5029fc2467dbd5cc5fe0c Mon Sep 17 00:00:00 2001 From: Whitney Date: Thu, 11 May 2023 16:17:25 -0700 Subject: [PATCH 06/20] Wave 4 Complete Thanks Ashley --- app/models/task.py | 2 + app/routes.py | 92 ++++++++++++++++++++++++++++++---------------- migrations/env.py | 4 ++ 3 files changed, 66 insertions(+), 32 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index a7164370e..947f151a7 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -32,3 +32,5 @@ def is_task_complete(self): else: self.completed_at = True return self.completed_at + + # def make_message(self): diff --git a/app/routes.py b/app/routes.py index fe5636a64..e11f383f0 100644 --- a/app/routes.py +++ b/app/routes.py @@ -3,6 +3,8 @@ from app.models.task import Task from datetime import datetime from app import db +import os + tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") @@ -104,35 +106,76 @@ def delete_task(model_id): #--------------------------------------------- + + # Send the message to Slack + # token = os.environ.get('SLACK_API_TOKEN') + # message = f"Someone just completed the task {new_task.title} My Beautiful Task" + # slack_message = { + # "text": message, + # "channel": "api-test-channel", + # "token": token + # } + # response = requests.post("https://slack.com/api/chat.postMessage", data=slack_message) + + + # db.session.commit() -@tasks_bp.route("/mark_complete", methods=["PATCH"]) -def mark_task_complete(model_id): + # return jsonify({"task": new_task.to_dict(), "slack_response": response.json()}), 200 + + +@tasks_bp.route("/mark_complete", methods=["PATCH"]) +def mark_task_complete(task_id): try: - new_task = validate_model(Task, model_id) + new_task = validate_model(Task, task_id) except: return jsonify({"Message": "Invalid id"}), 404 - # Send the message to Slack - message = f"Someone just completed the task {new_task.title} My Beautiful Task" - payload = { - "text": message, - "channel": "api-test-channel", - "token": "your_slack_token" - } - response = requests.post("https://slack.com/api/chat.postMessage", data=payload) + new_task.completed_at = datetime.utcnow() + + send_slack_message(new_task) + + db.session.commit() return jsonify({"task": new_task.to_dict()}), 200 + + +@tasks_bp.route("/mark_incomplete", methods=["PATCH"]) +def mark_task_incomplete(task_id): + try: + new_task = validate_model(Task, task_id) + except: + return jsonify({"Message": "Invalid id"}), 404 + + new_task.completed_at = None - # if: - # https://slack.com/api/chat.postMessage - # return "Someone just completed the task {task.title} My Beautiful Task" + db.session.commit() + return jsonify({"task": new_task.to_dict()}), 200 - # new_task.completed_at = datetime.utcnow() - # db.session.commit() - # return jsonify({"task": new_task.to_dict()}), 200 +def send_slack_message(completed_task): + TOKEN = os.environ['SLACK_API_TOKEN'] + AUTH_HEADERS = { + "Authorization": f"Bearer {TOKEN}" + } + CHANNEL_ID = "C0561UUDX4K" + SLACK_URL = "https://slack.com/api/chat.postMessage" + try: + message = f"Someone just completed the task {completed_task.title}" + payload = { + "channel": CHANNEL_ID, + "text": message + } + + requests.post(SLACK_URL, data = payload, headers = AUTH_HEADERS) + # r.json() + # return make_response(r.json(), 201) + + except: + print("There was an error making the call to Slack") + + # @tasks_bp.route("/mark_complete", methods=["PATCH"]) # def mark_task_complete(model_id): @@ -146,18 +189,3 @@ def mark_task_complete(model_id): # db.session.commit() # return jsonify({"task": new_task.to_dict()}), 200 - - -@tasks_bp.route("/mark_incomplete", methods=["PATCH"]) -def mark_task_incomplete(model_id): - try: - new_task = validate_model(Task, model_id) - except: - return jsonify({"Message": "Invalid id"}), 404 - - new_task.completed_at = None - - db.session.commit() - - return jsonify({"task": new_task.to_dict()}), 200 - diff --git a/migrations/env.py b/migrations/env.py index 8b3fb3353..1c870e77a 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -94,3 +94,7 @@ def process_revision_directives(context, revision, directives): run_migrations_offline() else: run_migrations_online() + + + + From 5017feb6cd3f5690e5342e80f0788c421b32623e Mon Sep 17 00:00:00 2001 From: Whitney Date: Thu, 11 May 2023 17:24:22 -0700 Subject: [PATCH 07/20] Routes completed pre testing via Postman --- app/__init__.py | 3 + app/models/goal.py | 11 ++ app/models/task.py | 2 +- app/routes.py | 113 +++++++++++++----- .../6cc23de38d65_initial_migration.py | 28 +++++ tests/test_wave_05.py | 10 +- 6 files changed, 131 insertions(+), 36 deletions(-) create mode 100644 migrations/versions/6cc23de38d65_initial_migration.py diff --git a/app/__init__.py b/app/__init__.py index 90f13d662..c3093098c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -32,5 +32,8 @@ def create_app(test_config=None): # Register Blueprints here from .routes import tasks_bp app.register_blueprint(tasks_bp) + + from .routes import goals_bp + app.register_blueprint(tasks_bp) return app diff --git a/app/models/goal.py b/app/models/goal.py index b0ed11dd8..1d183d107 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -3,3 +3,14 @@ class Goal(db.Model): goal_id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) + + @classmethod + def from_dict(cls, goal_data): + new_goal = cls(title=goal_data["title"]) + return new_goal + + def to_dict(self): + return dict( + id = self.id, + title = self.title) \ No newline at end of file diff --git a/app/models/task.py b/app/models/task.py index 947f151a7..6577582d1 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -33,4 +33,4 @@ def is_task_complete(self): self.completed_at = True return self.completed_at - # def make_message(self): + diff --git a/app/routes.py b/app/routes.py index e11f383f0..b85276259 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,12 +1,14 @@ from flask import Blueprint, jsonify, abort, make_response, request import requests from app.models.task import Task +from app.models.goal import Goal from datetime import datetime from app import db import os tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") +goals_bp = Blueprint("goals", __name__, url_prefix="/goals") #POST /tasks @tasks_bp.route("", methods=["POST"]) @@ -104,25 +106,7 @@ def delete_task(model_id): message = {"details": f"Task 1 \"{task.title}\" successfully deleted"} return make_response(message, 200) - -#--------------------------------------------- - - # Send the message to Slack - # token = os.environ.get('SLACK_API_TOKEN') - # message = f"Someone just completed the task {new_task.title} My Beautiful Task" - # slack_message = { - # "text": message, - # "channel": "api-test-channel", - # "token": token - # } - # response = requests.post("https://slack.com/api/chat.postMessage", data=slack_message) - - - # db.session.commit() - - # return jsonify({"task": new_task.to_dict(), "slack_response": response.json()}), 200 - - +#Patch //mark_complete @tasks_bp.route("/mark_complete", methods=["PATCH"]) def mark_task_complete(task_id): try: @@ -169,23 +153,92 @@ def send_slack_message(completed_task): } requests.post(SLACK_URL, data = payload, headers = AUTH_HEADERS) - # r.json() - # return make_response(r.json(), 201) except: print("There was an error making the call to Slack") +#----------------------------------------------------------- +# goals_bp = Blueprint("goals", __name__, url_prefix="/goals") + +def validate_model(cls, goal_id): + try: + goal_id = int(goal_id) + except: + abort(make_response({"details": "Invalid data"}, 400)) + + goal = cls.query.get(goal_id) + + if not goal: + message = {"details": "Invalid data"} + abort(make_response(message, 404)) + + return goal + +@goals_bp.route("", methods=["POST"]) +def create_goal(): + try: + request_body = request.get_json() + new_goal = Goal.from_dict(request_body) + except KeyError as err: + return make_response({"details": "Invalid data"}, 400) + + db.session.add(new_goal) + db.session.commit() + + return jsonify({"goal": new_goal.to_dict()}), 201 + + +@goals_bp.route("", methods=["GET"]) +def get_all_goals(): -# @tasks_bp.route("/mark_complete", methods=["PATCH"]) -# def mark_task_complete(model_id): -# try: -# new_task = validate_model(Task, model_id) -# except: -# return jsonify({"Message": "Invalid id"}), 404 + goals = Goal.query.all() + + goal_response = [] + for goal in goals: + goal_response.append(goal.to_dict()) + + return jsonify(goal_response), 200 + +@goals_bp.route("/", methods=["GET"]) +def get_one_goal(goal_id): + goal = validate_model(Goal, goal_id) + if goal: + return {"goal": goal.to_dict()}, 200 + + else: + return {'details': 'Invalid data'}, 404 + + +@goals_bp.route("", methods=["PUT"]) +def update_task(goal_id): + try: + goal = validate_model(Goal, goal_id) + except: + return jsonify({"Message": "Invalid id"}), 404 + + request_body = request.get_json() + + goal.title = request_body["title"] + + db.session.commit() + + return jsonify({"goal": goal.to_dict()}), 200 + + +@goals_bp.route("", methods=["DELETE"]) +def delete_goal(goal_id): + goal = validate_model(Goal, goal_id) + + if goal is None: + return {'details': 'Invalid data'}, 404 + + db.session.delete(goal) + db.session.commit() + + message = {"details": f"Task 1 \"{goal.title}\" successfully deleted"} + return make_response(message, 200) -# new_task.completed_at = datetime.utcnow() -# db.session.commit() -# return jsonify({"task": new_task.to_dict()}), 200 +########Change model_id to task_id ############ \ No newline at end of file diff --git a/migrations/versions/6cc23de38d65_initial_migration.py b/migrations/versions/6cc23de38d65_initial_migration.py new file mode 100644 index 000000000..b608daf23 --- /dev/null +++ b/migrations/versions/6cc23de38d65_initial_migration.py @@ -0,0 +1,28 @@ +"""Initial Migration + +Revision ID: 6cc23de38d65 +Revises: a91689b1b85e +Create Date: 2023-05-11 16:30:36.845767 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '6cc23de38d65' +down_revision = 'a91689b1b85e' +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..26d37fc4f 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -1,7 +1,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_goals_no_saved_goals(client): # Act response = client.get("/goals") @@ -12,7 +12,7 @@ def test_get_goals_no_saved_goals(client): assert response_body == [] -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_goals_one_saved_goal(client, one_goal): # Act response = client.get("/goals") @@ -29,7 +29,7 @@ def test_get_goals_one_saved_goal(client, one_goal): ] -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_goal(client, one_goal): # Act response = client.get("/goals/1") @@ -61,7 +61,7 @@ def test_get_goal_not_found(client): # ---- Complete Test ---- -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_create_goal(client): # Act response = client.post("/goals", json={ @@ -144,7 +144,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 0bf7e2304a5415d4729c70c14118e7cc8a4e3839 Mon Sep 17 00:00:00 2001 From: Whitney Date: Thu, 11 May 2023 20:33:58 -0700 Subject: [PATCH 08/20] Wave 5 Complete --- app/__init__.py | 2 +- app/models/goal.py | 2 +- app/routes.py | 37 ++++++---------------- tests/test_wave_01.py | 1 - tests/test_wave_05.py | 72 +++++++++++++++++++++++-------------------- 5 files changed, 51 insertions(+), 63 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index c3093098c..9e65a3c29 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -34,6 +34,6 @@ def create_app(test_config=None): app.register_blueprint(tasks_bp) from .routes import goals_bp - app.register_blueprint(tasks_bp) + app.register_blueprint(goals_bp) return app diff --git a/app/models/goal.py b/app/models/goal.py index 1d183d107..c2a79fb9b 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -12,5 +12,5 @@ def from_dict(cls, goal_data): def to_dict(self): return dict( - id = self.id, + id = self.goal_id, title = self.title) \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index b85276259..bfe92ce35 100644 --- a/app/routes.py +++ b/app/routes.py @@ -159,21 +159,6 @@ def send_slack_message(completed_task): #----------------------------------------------------------- -# goals_bp = Blueprint("goals", __name__, url_prefix="/goals") - -def validate_model(cls, goal_id): - try: - goal_id = int(goal_id) - except: - abort(make_response({"details": "Invalid data"}, 400)) - - goal = cls.query.get(goal_id) - - if not goal: - message = {"details": "Invalid data"} - abort(make_response(message, 404)) - - return goal @goals_bp.route("", methods=["POST"]) def create_goal(): @@ -200,6 +185,7 @@ def get_all_goals(): return jsonify(goal_response), 200 + @goals_bp.route("/", methods=["GET"]) def get_one_goal(goal_id): goal = validate_model(Goal, goal_id) @@ -228,17 +214,14 @@ def update_task(goal_id): @goals_bp.route("", methods=["DELETE"]) def delete_goal(goal_id): - goal = validate_model(Goal, goal_id) - - if goal is None: - return {'details': 'Invalid data'}, 404 - - db.session.delete(goal) - db.session.commit() - - message = {"details": f"Task 1 \"{goal.title}\" successfully deleted"} - return make_response(message, 200) - + try: + goal = validate_model(Goal, goal_id) + db.session.delete(goal) + db.session.commit() -########Change model_id to task_id ############ \ No newline at end of file + message = {"details": f"Goal 1 \"{goal.title}\" successfully deleted"} + return make_response(message, 200) + + except: + return {'details': 'Invalid data'}, 404 diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index 18e1aff9c..a81f8bc40 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -53,7 +53,6 @@ def test_get_task(client, one_task): } } - # @pytest.mark.skip(reason="No way to test this feature yet") def test_get_task_not_found(client): # Act diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index 26d37fc4f..f287d40bb 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -1,3 +1,4 @@ +from app.models.goal import Goal import pytest @@ -46,14 +47,17 @@ 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") + response.status_code == 404 + response_body == {'details': 'Invalid data'} + + # raise Exception("Complete test") # Assert # ---- Complete Test ---- # assertion 1 goes here @@ -80,34 +84,42 @@ 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 + + response = client.put("/goals/1", json={ + "title": "Update Goal"}) # ---- Complete Act Here ---- + 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": "Update Goal" + } + } + + goal=Goal.query.get(1) + assert goal.title == "Update Goal" -@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 ---- - - # Assert - # ---- Complete Assertions Here ---- - # assertion 1 goes here - # assertion 2 goes here - # ---- Complete Assertions Here ---- + response = client.put("/goals/2", json={ + "title": "Updated Goal", + }) + response_body = response.get_json() + + assert response.status_code == 404 + assert response_body == {"Message": "Invalid id"}, 404 -@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") @@ -122,26 +134,20 @@ def test_delete_goal(client, one_goal): # Check that the goal was deleted response = client.get("/goals/1") + response_body = response.get_json() assert response.status_code == 404 + assert response_body == {'details': 'Invalid data'} - raise Exception("Complete test with assertion about response body") - # ***************************************************************** - # **Complete test with assertion about 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/2") + 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 response_body == {'details': 'Invalid data'} # @pytest.mark.skip(reason="No way to test this feature yet") From e0df651e7f985b1e2d2be0976ceab98ff22f6ee1 Mon Sep 17 00:00:00 2001 From: Whitney Date: Thu, 11 May 2023 21:18:30 -0700 Subject: [PATCH 09/20] Added one to many attributes to models and migrated --- app/models/goal.py | 4 ++- app/models/task.py | 1 + app/routes.py | 4 +++ ...6a0a86_created_one_to_many_relationship.py | 30 +++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 migrations/versions/34b6ef6a0a86_created_one_to_many_relationship.py diff --git a/app/models/goal.py b/app/models/goal.py index c2a79fb9b..b0a766cee 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", backref="goal") @classmethod def from_dict(cls, goal_data): @@ -13,4 +14,5 @@ def from_dict(cls, goal_data): def to_dict(self): return dict( id = self.goal_id, - title = self.title) \ No newline at end of file + title = self.title) + \ No newline at end of file diff --git a/app/models/task.py b/app/models/task.py index 6577582d1..b4d145366 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -9,6 +9,7 @@ class Task(db.Model): description = db.Column(db.String, nullable=False) # completed_at = db.Column(db.DateTime, default = None) completed_at = db.Column(db.DateTime(timezone=True), nullable=True) + goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id")) @classmethod diff --git a/app/routes.py b/app/routes.py index bfe92ce35..89d479934 100644 --- a/app/routes.py +++ b/app/routes.py @@ -145,6 +145,7 @@ def send_slack_message(completed_task): } CHANNEL_ID = "C0561UUDX4K" SLACK_URL = "https://slack.com/api/chat.postMessage" + try: message = f"Someone just completed the task {completed_task.title}" payload = { @@ -225,3 +226,6 @@ def delete_goal(goal_id): except: return {'details': 'Invalid data'}, 404 + +#---------------------------------------------------------- + diff --git a/migrations/versions/34b6ef6a0a86_created_one_to_many_relationship.py b/migrations/versions/34b6ef6a0a86_created_one_to_many_relationship.py new file mode 100644 index 000000000..760f98b90 --- /dev/null +++ b/migrations/versions/34b6ef6a0a86_created_one_to_many_relationship.py @@ -0,0 +1,30 @@ +"""Created one to many relationship + +Revision ID: 34b6ef6a0a86 +Revises: 6cc23de38d65 +Create Date: 2023-05-11 21:04:32.850059 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '34b6ef6a0a86' +down_revision = '6cc23de38d65' +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 ### From a850bab472955771d130dfd307f8150336e3ae30 Mon Sep 17 00:00:00 2001 From: Whitney Date: Fri, 12 May 2023 09:32:45 -0700 Subject: [PATCH 10/20] Wave 6 in process --- app/models/goal.py | 9 +++++-- app/models/task.py | 2 +- app/routes.py | 62 +++++++++++++++++++++++++++++++++++++++++-- tests/test_wave_06.py | 10 +++---- 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/app/models/goal.py b/app/models/goal.py index b0a766cee..dbc787b2d 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -4,7 +4,7 @@ class Goal(db.Model): goal_id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) - tasks = db.relationship("Task", backref="goal") + tasks = db.relationship("Task", backref="goal", lazy = True) @classmethod def from_dict(cls, goal_data): @@ -15,4 +15,9 @@ def to_dict(self): return dict( id = self.goal_id, title = self.title) - \ No newline at end of file + + def goal_task_dict(self): + return dict( + id = self.goal_id, + task_ids = self.tasks + ) diff --git a/app/models/task.py b/app/models/task.py index b4d145366..6624ec08a 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -9,7 +9,7 @@ class Task(db.Model): description = db.Column(db.String, nullable=False) # completed_at = db.Column(db.DateTime, default = None) completed_at = db.Column(db.DateTime(timezone=True), nullable=True) - goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id")) + goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True) @classmethod diff --git a/app/routes.py b/app/routes.py index 89d479934..d941d7fd5 100644 --- a/app/routes.py +++ b/app/routes.py @@ -6,9 +6,10 @@ from app import db import os - -tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") goals_bp = Blueprint("goals", __name__, url_prefix="/goals") +tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") + + #POST /tasks @tasks_bp.route("", methods=["POST"]) @@ -229,3 +230,60 @@ def delete_goal(goal_id): #---------------------------------------------------------- +@goals_bp.route("//tasks", methods=['POST']) +def create_goal_with_tasks(goal_id): + + goal = Goal.query.get(goal_id) + + if goal is None: + return jsonify({"error": "Goal not found"}), 404 + + task_ids = request.json.get("task_ids", []) + + for task_id in task_ids: + task = Task.query.get(task_id) + if task: + goal.tasks.append(task) + + db.session.commit() + + response_body = {"id": goal.goal_id, "task_ids": task_ids} + return jsonify(response_body) + + # task_ids = request.json['task_ids'] + # goal = Goal() + + # for task_id in task_ids: + # task = Task.query.get(task_id) + # if task: + # goal.task_ids.append(task) + + # db.session.add(goal) + # db.session.commit() + + # response_body = {'id': goal.model_id, 'task_ids': task_ids} + # return jsonify(response_body) + +@goals_bp.route("//tasks", methods=['GET']) +def get_all_tasks_one_goal(goal_id): + + goal_id = request.args.get("id") + #validate? + tasks = Task.query.filter_by(goal_id=goal_id) + + tasks_response = [] + for task in tasks: + tasks_response.append({ + "id": task.task_id, + "goal_id": goal_id, + "title": goal_id.title, + "description": task.title, + "is_complete": task.completed_at + }) + + return { + "id": goal_id, + # "title": goal.title, + "tasks": [tasks_response] + } + \ No newline at end of file diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index 8afa4325e..ebfcbac67 100644 --- a/tests/test_wave_06.py +++ b/tests/test_wave_06.py @@ -2,7 +2,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_post_task_ids_to_goal(client, one_goal, three_tasks): # Act response = client.post("/goals/1/tasks", json={ @@ -23,7 +23,7 @@ def test_post_task_ids_to_goal(client, one_goal, three_tasks): assert len(Goal.query.get(1).tasks) == 3 -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_post_task_ids_to_goal_already_with_goals(client, one_task_belongs_to_one_goal, three_tasks): # Act response = client.post("/goals/1/tasks", json={ @@ -57,7 +57,7 @@ def test_get_tasks_for_specific_goal_no_goal(client): # ***************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_for_specific_goal_no_tasks(client, one_goal): # Act response = client.get("/goals/1/tasks") @@ -74,7 +74,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 +99,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 bc5922b6d60a58536aa2e123f27d6b442b938892 Mon Sep 17 00:00:00 2001 From: Whitney Date: Sat, 13 May 2023 18:34:45 -0700 Subject: [PATCH 11/20] All tests for POST passing --- app/models/goal.py | 12 ++++---- app/models/task.py | 11 +++++++ app/routes.py | 68 +++++++++++++++++++++++++------------------ tests/test_wave_06.py | 16 ++++++++-- 4 files changed, 71 insertions(+), 36 deletions(-) diff --git a/app/models/goal.py b/app/models/goal.py index dbc787b2d..4acb1de9f 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -6,6 +6,8 @@ class Goal(db.Model): title = db.Column(db.String) tasks = db.relationship("Task", backref="goal", lazy = True) + # tasks = db.relationship("Task", back_populates="goal", lazy = True) + @classmethod def from_dict(cls, goal_data): new_goal = cls(title=goal_data["title"]) @@ -16,8 +18,8 @@ def to_dict(self): id = self.goal_id, title = self.title) - def goal_task_dict(self): - return dict( - id = self.goal_id, - task_ids = self.tasks - ) + # def goal_task_dict(self): + # return dict( + # id = self.goal_id, + # task_ids = self.tasks + # ) diff --git a/app/models/task.py b/app/models/task.py index 6624ec08a..438803855 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -19,6 +19,15 @@ def from_dict(cls, task_data): completed_at=None) return new_task + # @classmethod + # def from_dict(cls, request_body): + # return cls( + # title = request_body["title"], + # description = request_body["description"], + # completed_at = request_body.get("completed_at"), + # goal_id = request_body.get("goal_id") + # ) + def to_dict(self): return dict( id = self.id, @@ -27,6 +36,8 @@ def to_dict(self): is_complete = self.is_task_complete() ) + + def is_task_complete(self): if self.completed_at == None: self.completed_at = False diff --git a/app/routes.py b/app/routes.py index d941d7fd5..278dddabe 100644 --- a/app/routes.py +++ b/app/routes.py @@ -9,6 +9,9 @@ goals_bp = Blueprint("goals", __name__, url_prefix="/goals") tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") +goals_bp = Blueprint("goals", __name__, url_prefix="/goals") +tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") + #POST /tasks @@ -233,42 +236,31 @@ def delete_goal(goal_id): @goals_bp.route("//tasks", methods=['POST']) def create_goal_with_tasks(goal_id): - goal = Goal.query.get(goal_id) - - if goal is None: - return jsonify({"error": "Goal not found"}), 404 - - task_ids = request.json.get("task_ids", []) + request_body = request.get_json() + goal = validate_model(Goal, goal_id) + task_list = request_body.get("task_ids") - for task_id in task_ids: - task = Task.query.get(task_id) - if task: - goal.tasks.append(task) + tasks = [] + for task_id in task_list: + task = validate_model(Task, task_id) + task.goal = goal + tasks.append(task_id) db.session.commit() - response_body = {"id": goal.goal_id, "task_ids": task_ids} - return jsonify(response_body) - - # task_ids = request.json['task_ids'] - # goal = Goal() - - # for task_id in task_ids: - # task = Task.query.get(task_id) - # if task: - # goal.task_ids.append(task) - - # db.session.add(goal) - # db.session.commit() + message = { + "id": goal.goal_id, + "task_ids": tasks + } + + return make_response(message, 200) - # response_body = {'id': goal.model_id, 'task_ids': task_ids} - # return jsonify(response_body) @goals_bp.route("//tasks", methods=['GET']) def get_all_tasks_one_goal(goal_id): - goal_id = request.args.get("id") - #validate? + goal = validate_model(Goal, goal_id) + tasks = Task.query.filter_by(goal_id=goal_id) tasks_response = [] @@ -286,4 +278,24 @@ def get_all_tasks_one_goal(goal_id): # "title": goal.title, "tasks": [tasks_response] } - \ No newline at end of file + + +@tasks_bp.route("", methods=["GET"]) +def get_all_tasks(): + + sort_query = request.args.get("sort") + + if sort_query == "asc": + tasks = Task.query.order_by(Task.title) + + elif sort_query == "desc": + tasks = Task.query.order_by(Task.title.desc()) + + else: + tasks = Task.query.all() + + tasks_response = [] + for task in tasks: + tasks_response.append(task.to_dict()) + + return jsonify(tasks_response), 200 \ No newline at end of file diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index ebfcbac67..2770bb0fe 100644 --- a/tests/test_wave_06.py +++ b/tests/test_wave_06.py @@ -22,6 +22,8 @@ def test_post_task_ids_to_goal(client, one_goal, three_tasks): # Check that Goal was updated in the db assert len(Goal.query.get(1).tasks) == 3 + print(response_body) + # @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): @@ -41,6 +43,8 @@ def test_post_task_ids_to_goal_already_with_goals(client, one_task_belongs_to_on } assert len(Goal.query.get(1).tasks) == 2 + print(response_body) + @pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_for_specific_goal_no_goal(client): @@ -57,11 +61,12 @@ def test_get_tasks_for_specific_goal_no_goal(client): # ***************************************************************** -# @pytest.mark.skip(reason="No way to test this feature yet") +@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_for_specific_goal_no_tasks(client, one_goal): # Act response = client.get("/goals/1/tasks") response_body = response.get_json() + print(response) # Assert assert response.status_code == 200 @@ -72,9 +77,10 @@ def test_get_tasks_for_specific_goal_no_tasks(client, one_goal): "title": "Build a habit of going outside daily", "tasks": [] } + print(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(client, one_task_belongs_to_one_goal): # Act response = client.get("/goals/1/tasks") @@ -98,8 +104,10 @@ def test_get_tasks_for_specific_goal(client, one_task_belongs_to_one_goal): ] } + print(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_task_includes_goal_id(client, one_task_belongs_to_one_goal): response = client.get("/tasks/1") response_body = response.get_json() @@ -116,3 +124,5 @@ def test_get_task_includes_goal_id(client, one_task_belongs_to_one_goal): "is_complete": False } } + + print(response_body) From 9d08f6d5dbf4678667af093424f63cdacd2e6db3 Mon Sep 17 00:00:00 2001 From: Whitney Date: Sun, 14 May 2023 12:17:17 -0700 Subject: [PATCH 12/20] all tests passing all waves --- app/models/goal.py | 25 ++++++--- app/models/task.py | 46 +++++++++++----- app/routes.py | 118 +++++++++++++++++++++++++----------------- tests/test_wave_06.py | 20 +++---- 4 files changed, 131 insertions(+), 78 deletions(-) diff --git a/app/models/goal.py b/app/models/goal.py index 4acb1de9f..4bea40de2 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -1,10 +1,11 @@ from app import db + class Goal(db.Model): goal_id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) - tasks = db.relationship("Task", backref="goal", lazy = True) + tasks = db.relationship("Task",backref="goal", lazy = True) # tasks = db.relationship("Task", back_populates="goal", lazy = True) @@ -18,8 +19,20 @@ def to_dict(self): id = self.goal_id, title = self.title) - # def goal_task_dict(self): - # return dict( - # id = self.goal_id, - # task_ids = self.tasks - # ) + # from app.models.task import Task + + def connected_dict(self, task_data): + return dict( + id = self.goal_id, + title = self.title, + tasks = [ + dict( + id = task_data["id"], + goal_id = self.goal_id, + title = task_data["title"], + description = task_data["description"], + completed_at = task_data["completed_at"] + ) + ] + ) + \ No newline at end of file diff --git a/app/models/task.py b/app/models/task.py index 438803855..2d035050b 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -9,6 +9,7 @@ class Task(db.Model): description = db.Column(db.String, nullable=False) # completed_at = db.Column(db.DateTime, default = None) completed_at = db.Column(db.DateTime(timezone=True), nullable=True) + # goal = db.relationship("Goal") goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True) @@ -19,30 +20,49 @@ def from_dict(cls, task_data): completed_at=None) return new_task - # @classmethod - # def from_dict(cls, request_body): - # return cls( - # title = request_body["title"], - # description = request_body["description"], - # completed_at = request_body.get("completed_at"), - # goal_id = request_body.get("goal_id") - # ) def to_dict(self): - return dict( + if self.goal_id: + + return dict( id = self.id, + goal_id = self.goal_id, title = self.title, description = self.description, is_complete = self.is_task_complete() ) - + return dict( + id = self.id, + title = self.title, + description = self.description, + is_complete = self.is_task_complete() + ) def is_task_complete(self): if self.completed_at == None: - self.completed_at = False + return False else: - self.completed_at = True - return self.completed_at + return True + + from app.models.goal import Goal + def to_goal_id_dict(self, goal_id): + return dict( + id = self.id, + goal_id = goal_id, + title = self.title, + description = self.description, + is_complete = self.is_task_complete() + ) + # from app.models.goal import Goal + # def to_dict_with_goal(self, goal_id): + # return dict( + # id = self.id, + # goal_id = int(goal_id), + # title = self.title, + # description = self.description, + # is_complete = self.is_task_complete() + # ) + diff --git a/app/routes.py b/app/routes.py index 278dddabe..2bdc01f2f 100644 --- a/app/routes.py +++ b/app/routes.py @@ -9,9 +9,6 @@ goals_bp = Blueprint("goals", __name__, url_prefix="/goals") tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") -goals_bp = Blueprint("goals", __name__, url_prefix="/goals") -tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") - #POST /tasks @@ -30,13 +27,13 @@ def create_task(): #Validate Model -def validate_model(cls, model_id): +def validate_model(cls, task_id): try: - model_id = int(model_id) + task_id = int(task_id) except: abort(make_response({"details": "Invalid data"}, 400)) - task = cls.query.get(model_id) + task = cls.query.get(task_id) if not task: message = {"details": "Invalid data"} @@ -68,9 +65,9 @@ def get_all_tasks(): #GET /tasks/ -@tasks_bp.route("/", methods=["GET"]) -def get_one_task(model_id): - task = validate_model(Task, model_id) +@tasks_bp.route("/", methods=["GET"]) +def get_one_task(task_id): + task = validate_model(Task, task_id) if task: return {"task": task.to_dict()}, 200 @@ -79,10 +76,10 @@ def get_one_task(model_id): #PUT /tasks/ -@tasks_bp.route("", methods=["PUT"]) -def update_task(model_id): +@tasks_bp.route("", methods=["PUT"]) +def update_task(task_id): try: - task = validate_model(Task, model_id) + task = validate_model(Task, task_id) except: return jsonify({"Message": "Invalid id"}), 404 @@ -97,9 +94,9 @@ def update_task(model_id): #DELETE /tasks/ -@tasks_bp.route("", methods=["DELETE"]) -def delete_task(model_id): - task = validate_model(Task, model_id) +@tasks_bp.route("", methods=["DELETE"]) +def delete_task(task_id): + task = validate_model(Task, task_id) if task is None: return {'details': 'Invalid data'}, 404 @@ -258,44 +255,71 @@ def create_goal_with_tasks(goal_id): @goals_bp.route("//tasks", methods=['GET']) def get_all_tasks_one_goal(goal_id): + try: + goal = validate_model(Goal, goal_id) + except: + return make_response({"details": "Invalid data"}, 404) - goal = validate_model(Goal, goal_id) - - tasks = Task.query.filter_by(goal_id=goal_id) + # tasks = Task.query.filter_by(goal_id=goal_id) + + task_list = [] + + for task in goal.tasks: + task = validate_model(Task, task.id) + # task_dict = task.to_dict() + # task_dict[goal_id] = goal_id + # task_list.append(task.to_goal_id_dict()) + # task = { + # "id": task.id, + # "goal_id": goal.goal_id, + # "title": task.title, + # "description": task.description, + # "is_complete": task. + # } + + task_list.append(task.to_dict()) + - tasks_response = [] - for task in tasks: - tasks_response.append({ - "id": task.task_id, - "goal_id": goal_id, - "title": goal_id.title, - "description": task.title, - "is_complete": task.completed_at - }) - - return { - "id": goal_id, - # "title": goal.title, - "tasks": [tasks_response] - } - + # for task in goal.tasks: + # task = validate_model(Task, task.id) + # task_list.append(task.to_dict()) -@tasks_bp.route("", methods=["GET"]) -def get_all_tasks(): + message = { + "id": goal.goal_id, + "title": goal.title, + "tasks": task_list + } - sort_query = request.args.get("sort") + # message = { + # "id": goal.goal_id, + # "title": goal.title, + # "tasks": [{ + # "id": task.id, + # "goal_id": goal.goal_id, + # "title": task.title, + # "description": task.description, + # "is_complete": task.completed_at + # }] + # } - if sort_query == "asc": - tasks = Task.query.order_by(Task.title) + return make_response((message, 200)) + +# @tasks_bp.route("", methods=["GET"]) +# def get_all_tasks(): + +# sort_query = request.args.get("sort") + +# if sort_query == "asc": +# tasks = Task.query.order_by(Task.title) - elif sort_query == "desc": - tasks = Task.query.order_by(Task.title.desc()) +# elif sort_query == "desc": +# tasks = Task.query.order_by(Task.title.desc()) - else: - tasks = Task.query.all() +# else: +# tasks = Task.query.all() - tasks_response = [] - for task in tasks: - tasks_response.append(task.to_dict()) +# tasks_response = [] +# for task in tasks: +# tasks_response.append(task.to_dict()) - return jsonify(tasks_response), 200 \ No newline at end of file +# return jsonify(tasks_response), 200 \ No newline at end of file diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index 2770bb0fe..209a57a6d 100644 --- a/tests/test_wave_06.py +++ b/tests/test_wave_06.py @@ -22,7 +22,6 @@ def test_post_task_ids_to_goal(client, one_goal, three_tasks): # Check that Goal was updated in the db assert len(Goal.query.get(1).tasks) == 3 - print(response_body) # @pytest.mark.skip(reason="No way to test this feature yet") @@ -43,10 +42,8 @@ def test_post_task_ids_to_goal_already_with_goals(client, one_task_belongs_to_on } assert len(Goal.query.get(1).tasks) == 2 - print(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_goal(client): # Act response = client.get("/goals/1/tasks") @@ -54,19 +51,20 @@ def test_get_tasks_for_specific_goal_no_goal(client): # Assert assert response.status_code == 404 + assert response_body == {"details": "Invalid data"} - 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*************** # ***************************************************************** -@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") response_body = response.get_json() - print(response) + print(response_body) # Assert assert response.status_code == 200 @@ -80,11 +78,12 @@ def test_get_tasks_for_specific_goal_no_tasks(client, one_goal): print(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(client, one_task_belongs_to_one_goal): # Act response = client.get("/goals/1/tasks") response_body = response.get_json() + print(response_body) # Assert assert response.status_code == 200 @@ -104,10 +103,9 @@ def test_get_tasks_for_specific_goal(client, one_task_belongs_to_one_goal): ] } - print(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_task_includes_goal_id(client, one_task_belongs_to_one_goal): response = client.get("/tasks/1") response_body = response.get_json() @@ -124,5 +122,3 @@ def test_get_task_includes_goal_id(client, one_task_belongs_to_one_goal): "is_complete": False } } - - print(response_body) From e3f10b7b7ac28f846712c7a65734751ade7e4496 Mon Sep 17 00:00:00 2001 From: Whitney Date: Sun, 14 May 2023 12:35:53 -0700 Subject: [PATCH 13/20] Updated backref to backpopulates --- app/models/goal.py | 3 ++- app/models/task.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/goal.py b/app/models/goal.py index 4bea40de2..a96c5d4a3 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -5,7 +5,8 @@ class Goal(db.Model): goal_id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) - tasks = db.relationship("Task",backref="goal", lazy = True) + # tasks = db.relationship("Task",backref="goal", lazy = True) + tasks = db.relationship("Task", back_populates="goal") # tasks = db.relationship("Task", back_populates="goal", lazy = True) diff --git a/app/models/task.py b/app/models/task.py index 2d035050b..59fbc8499 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -9,8 +9,9 @@ class Task(db.Model): description = db.Column(db.String, nullable=False) # completed_at = db.Column(db.DateTime, default = None) completed_at = db.Column(db.DateTime(timezone=True), nullable=True) - # goal = db.relationship("Goal") goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True) + goal = db.relationship("Goal", back_populates="tasks") + @classmethod From 5f448eb968ac6d57cba291802d78321f85f52bf5 Mon Sep 17 00:00:00 2001 From: Whitney Date: Sun, 14 May 2023 13:45:09 -0700 Subject: [PATCH 14/20] Replaced routes file with goal_routes and task_routes ro utes file still present but commented out --- app/__init__.py | 4 +- app/models/task.py | 4 +- app/routes.py | 325 -------------------------------------- app/routes/__init__.py | 0 app/routes/goal_routes.py | 120 ++++++++++++++ app/routes/helpers.py | 22 +++ app/routes/routes.py | 273 ++++++++++++++++++++++++++++++++ app/routes/task_routes.py | 157 ++++++++++++++++++ 8 files changed, 576 insertions(+), 329 deletions(-) delete mode 100644 app/routes.py create mode 100644 app/routes/__init__.py create mode 100644 app/routes/goal_routes.py create mode 100644 app/routes/helpers.py create mode 100644 app/routes/routes.py create mode 100644 app/routes/task_routes.py diff --git a/app/__init__.py b/app/__init__.py index 9e65a3c29..3e097d676 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,10 +30,10 @@ def create_app(test_config=None): migrate.init_app(app, db) # Register Blueprints here - from .routes import tasks_bp + from .routes.task_routes import tasks_bp app.register_blueprint(tasks_bp) - from .routes import goals_bp + from .routes.goal_routes import goals_bp app.register_blueprint(goals_bp) return app diff --git a/app/models/task.py b/app/models/task.py index 59fbc8499..969abb921 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -5,8 +5,8 @@ class Task(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) - title = db.Column(db.String, nullable=False) - description = db.Column(db.String, nullable=False) + title = db.Column(db.String) # nullable=False) + description = db.Column(db.String) #nullable=False) # completed_at = db.Column(db.DateTime, default = None) completed_at = db.Column(db.DateTime(timezone=True), nullable=True) goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True) diff --git a/app/routes.py b/app/routes.py deleted file mode 100644 index 2bdc01f2f..000000000 --- a/app/routes.py +++ /dev/null @@ -1,325 +0,0 @@ -from flask import Blueprint, jsonify, abort, make_response, request -import requests -from app.models.task import Task -from app.models.goal import Goal -from datetime import datetime -from app import db -import os - -goals_bp = Blueprint("goals", __name__, url_prefix="/goals") -tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") - - - -#POST /tasks -@tasks_bp.route("", methods=["POST"]) -def create_task(): - try: - request_body = request.get_json() - new_task = Task.from_dict(request_body) - except KeyError as err: - return make_response({"details": "Invalid data"}, 400) - - db.session.add(new_task) - db.session.commit() - - return jsonify({"task": new_task.to_dict()}), 201 - - -#Validate Model -def validate_model(cls, task_id): - try: - task_id = int(task_id) - except: - abort(make_response({"details": "Invalid data"}, 400)) - - task = cls.query.get(task_id) - - if not task: - message = {"details": "Invalid data"} - abort(make_response(message, 404)) - - return task - - -#GET /tasks -@tasks_bp.route("", methods=["GET"]) -def get_all_tasks(): - - sort_query = request.args.get("sort") - - if sort_query == "asc": - tasks = Task.query.order_by(Task.title) - - elif sort_query == "desc": - tasks = Task.query.order_by(Task.title.desc()) - - else: - tasks = Task.query.all() - - tasks_response = [] - for task in tasks: - tasks_response.append(task.to_dict()) - - return jsonify(tasks_response), 200 - - -#GET /tasks/ -@tasks_bp.route("/", methods=["GET"]) -def get_one_task(task_id): - task = validate_model(Task, task_id) - if task: - return {"task": task.to_dict()}, 200 - - else: - return {'details': 'Invalid data'}, 404 - - -#PUT /tasks/ -@tasks_bp.route("", methods=["PUT"]) -def update_task(task_id): - try: - task = validate_model(Task, task_id) - except: - return jsonify({"Message": "Invalid id"}), 404 - - request_body = request.get_json() - - task.title = request_body["title"] - task.description = request_body["description"] - - db.session.commit() - - return jsonify({"task": task.to_dict()}), 200 - - -#DELETE /tasks/ -@tasks_bp.route("", methods=["DELETE"]) -def delete_task(task_id): - task = validate_model(Task, task_id) - - if task is None: - return {'details': 'Invalid data'}, 404 - - db.session.delete(task) - db.session.commit() - - message = {"details": f"Task 1 \"{task.title}\" successfully deleted"} - return make_response(message, 200) - -#Patch //mark_complete -@tasks_bp.route("/mark_complete", methods=["PATCH"]) -def mark_task_complete(task_id): - try: - new_task = validate_model(Task, task_id) - except: - return jsonify({"Message": "Invalid id"}), 404 - - new_task.completed_at = datetime.utcnow() - - send_slack_message(new_task) - - db.session.commit() - - return jsonify({"task": new_task.to_dict()}), 200 - - -@tasks_bp.route("/mark_incomplete", methods=["PATCH"]) -def mark_task_incomplete(task_id): - try: - new_task = validate_model(Task, task_id) - except: - return jsonify({"Message": "Invalid id"}), 404 - - new_task.completed_at = None - - db.session.commit() - - return jsonify({"task": new_task.to_dict()}), 200 - - - -def send_slack_message(completed_task): - TOKEN = os.environ['SLACK_API_TOKEN'] - AUTH_HEADERS = { - "Authorization": f"Bearer {TOKEN}" - } - CHANNEL_ID = "C0561UUDX4K" - SLACK_URL = "https://slack.com/api/chat.postMessage" - - try: - message = f"Someone just completed the task {completed_task.title}" - payload = { - "channel": CHANNEL_ID, - "text": message - } - - requests.post(SLACK_URL, data = payload, headers = AUTH_HEADERS) - - except: - print("There was an error making the call to Slack") - - -#----------------------------------------------------------- - -@goals_bp.route("", methods=["POST"]) -def create_goal(): - try: - request_body = request.get_json() - new_goal = Goal.from_dict(request_body) - except KeyError as err: - return make_response({"details": "Invalid data"}, 400) - - db.session.add(new_goal) - db.session.commit() - - return jsonify({"goal": new_goal.to_dict()}), 201 - - -@goals_bp.route("", methods=["GET"]) -def get_all_goals(): - - goals = Goal.query.all() - - goal_response = [] - for goal in goals: - goal_response.append(goal.to_dict()) - - return jsonify(goal_response), 200 - - -@goals_bp.route("/", methods=["GET"]) -def get_one_goal(goal_id): - goal = validate_model(Goal, goal_id) - if goal: - return {"goal": goal.to_dict()}, 200 - - else: - return {'details': 'Invalid data'}, 404 - - -@goals_bp.route("", methods=["PUT"]) -def update_task(goal_id): - try: - goal = validate_model(Goal, goal_id) - except: - return jsonify({"Message": "Invalid id"}), 404 - - request_body = request.get_json() - - goal.title = request_body["title"] - - db.session.commit() - - return jsonify({"goal": goal.to_dict()}), 200 - - -@goals_bp.route("", methods=["DELETE"]) -def delete_goal(goal_id): - try: - goal = validate_model(Goal, goal_id) - - db.session.delete(goal) - db.session.commit() - - message = {"details": f"Goal 1 \"{goal.title}\" successfully deleted"} - return make_response(message, 200) - - except: - return {'details': 'Invalid data'}, 404 - -#---------------------------------------------------------- - -@goals_bp.route("//tasks", methods=['POST']) -def create_goal_with_tasks(goal_id): - - request_body = request.get_json() - goal = validate_model(Goal, goal_id) - task_list = request_body.get("task_ids") - - tasks = [] - for task_id in task_list: - task = validate_model(Task, task_id) - task.goal = goal - tasks.append(task_id) - - db.session.commit() - - message = { - "id": goal.goal_id, - "task_ids": tasks - } - - return make_response(message, 200) - - -@goals_bp.route("//tasks", methods=['GET']) -def get_all_tasks_one_goal(goal_id): - try: - goal = validate_model(Goal, goal_id) - except: - return make_response({"details": "Invalid data"}, 404) - - # tasks = Task.query.filter_by(goal_id=goal_id) - - task_list = [] - - for task in goal.tasks: - task = validate_model(Task, task.id) - # task_dict = task.to_dict() - # task_dict[goal_id] = goal_id - # task_list.append(task.to_goal_id_dict()) - # task = { - # "id": task.id, - # "goal_id": goal.goal_id, - # "title": task.title, - # "description": task.description, - # "is_complete": task. - # } - - task_list.append(task.to_dict()) - - - # for task in goal.tasks: - # task = validate_model(Task, task.id) - # task_list.append(task.to_dict()) - - message = { - "id": goal.goal_id, - "title": goal.title, - "tasks": task_list - } - - # message = { - # "id": goal.goal_id, - # "title": goal.title, - # "tasks": [{ - # "id": task.id, - # "goal_id": goal.goal_id, - # "title": task.title, - # "description": task.description, - # "is_complete": task.completed_at - # }] - # } - - return make_response((message, 200)) - -# @tasks_bp.route("", methods=["GET"]) -# def get_all_tasks(): - -# sort_query = request.args.get("sort") - -# if sort_query == "asc": -# tasks = Task.query.order_by(Task.title) - -# elif sort_query == "desc": -# tasks = Task.query.order_by(Task.title.desc()) - -# else: -# tasks = Task.query.all() - -# tasks_response = [] -# for task in tasks: -# tasks_response.append(task.to_dict()) - -# return jsonify(tasks_response), 200 \ No newline at end of file diff --git a/app/routes/__init__.py b/app/routes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/routes/goal_routes.py b/app/routes/goal_routes.py new file mode 100644 index 000000000..bed1cc9be --- /dev/null +++ b/app/routes/goal_routes.py @@ -0,0 +1,120 @@ +from flask import Blueprint, jsonify, abort, make_response, request +import requests +from app.models.goal import Goal +from app.models.task import Task +from app.routes.helpers import validate_model +from datetime import datetime +from app import db +import os + +goals_bp = Blueprint("goals", __name__, url_prefix="/goals") + +@goals_bp.route("", methods=["POST"]) +def create_goal(): + try: + request_body = request.get_json() + new_goal = Goal.from_dict(request_body) + except KeyError as err: + return make_response({"details": "Invalid data"}, 400) + + db.session.add(new_goal) + db.session.commit() + + return jsonify({"goal": new_goal.to_dict()}), 201 + + +@goals_bp.route("", methods=["GET"]) +def get_all_goals(): + + goals = Goal.query.all() + + goal_response = [] + for goal in goals: + goal_response.append(goal.to_dict()) + + return jsonify(goal_response), 200 + + +@goals_bp.route("/", methods=["GET"]) +def get_one_goal(goal_id): + goal = validate_model(Goal, goal_id) + if goal: + return {"goal": goal.to_dict()}, 200 + + else: + return {'details': 'Invalid data'}, 404 + + +@goals_bp.route("", methods=["PUT"]) +def update_task(goal_id): + try: + goal = validate_model(Goal, goal_id) + except: + return jsonify({"Message": "Invalid id"}), 404 + + request_body = request.get_json() + + goal.title = request_body["title"] + + db.session.commit() + + return jsonify({"goal": goal.to_dict()}), 200 + + +@goals_bp.route("", methods=["DELETE"]) +def delete_goal(goal_id): + try: + goal = validate_model(Goal, goal_id) + + db.session.delete(goal) + db.session.commit() + + message = {"details": f"Goal 1 \"{goal.title}\" successfully deleted"} + return make_response(message, 200) + + except: + return {'details': 'Invalid data'}, 404 + +#---------------------------------------------------------- + +@goals_bp.route("//tasks", methods=['POST']) +def create_goal_with_tasks(goal_id): + + request_body = request.get_json() + goal = validate_model(Goal, goal_id) + task_list = request_body.get("task_ids") + + tasks = [] + for task_id in task_list: + task = validate_model(Task, task_id) + task.goal = goal + tasks.append(task_id) + + db.session.commit() + + message = { + "id": goal.goal_id, + "task_ids": tasks + } + return make_response(message, 200) + + +@goals_bp.route("//tasks", methods=['GET']) +def get_all_tasks_one_goal(goal_id): + try: + goal = validate_model(Goal, goal_id) + except: + return make_response({"details": "Invalid data"}, 404) + + task_list = [] + + for task in goal.tasks: + task = validate_model(Task, task.id) + task_list.append(task.to_dict()) + + message = { + "id": goal.goal_id, + "title": goal.title, + "tasks": task_list + } + return make_response((message, 200)) \ No newline at end of file diff --git a/app/routes/helpers.py b/app/routes/helpers.py new file mode 100644 index 000000000..8ef758c19 --- /dev/null +++ b/app/routes/helpers.py @@ -0,0 +1,22 @@ +from flask import Blueprint, jsonify, abort, make_response, request +import requests +from app.models.goal import Goal +from app.models.task import Task +from datetime import datetime +from app import db +import os + + +def validate_model(cls, task_id): + try: + task_id = int(task_id) + except: + abort(make_response({"details": "Invalid data"}, 400)) + + task = cls.query.get(task_id) + + if not task: + message = {"details": "Invalid data"} + abort(make_response(message, 404)) + + return task \ No newline at end of file diff --git a/app/routes/routes.py b/app/routes/routes.py new file mode 100644 index 000000000..fa4805066 --- /dev/null +++ b/app/routes/routes.py @@ -0,0 +1,273 @@ +# from flask import Blueprint, jsonify, abort, make_response, request +# import requests +# from app.models.task import Task +# from app.models.goal import Goal +# from datetime import datetime +# from app import db +# import os + +# goals_bp = Blueprint("goals", __name__, url_prefix="/goals") +# tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") + + + +# #POST /tasks +# @tasks_bp.route("", methods=["POST"]) +# def create_task(): +# try: +# request_body = request.get_json() +# new_task = Task.from_dict(request_body) +# except KeyError as err: +# return make_response({"details": "Invalid data"}, 400) + +# db.session.add(new_task) +# db.session.commit() + +# return jsonify({"task": new_task.to_dict()}), 201 + + +# #Validate Model +# def validate_model(cls, task_id): +# try: +# task_id = int(task_id) +# except: +# abort(make_response({"details": "Invalid data"}, 400)) + +# task = cls.query.get(task_id) + +# if not task: +# message = {"details": "Invalid data"} +# abort(make_response(message, 404)) + +# return task + + +# #GET /tasks +# @tasks_bp.route("", methods=["GET"]) +# def get_all_tasks(): + +# sort_query = request.args.get("sort") + +# if sort_query == "asc": +# tasks = Task.query.order_by(Task.title) + +# elif sort_query == "desc": +# tasks = Task.query.order_by(Task.title.desc()) + +# else: +# tasks = Task.query.all() + +# tasks_response = [] +# for task in tasks: +# tasks_response.append(task.to_dict()) + +# return jsonify(tasks_response), 200 + + +# #GET /tasks/ +# @tasks_bp.route("/", methods=["GET"]) +# def get_one_task(task_id): +# task = validate_model(Task, task_id) +# if task: +# return {"task": task.to_dict()}, 200 + +# else: +# return {'details': 'Invalid data'}, 404 + + +# #PUT /tasks/ +# @tasks_bp.route("", methods=["PUT"]) +# def update_task(task_id): +# try: +# task = validate_model(Task, task_id) +# except: +# return jsonify({"Message": "Invalid id"}), 404 + +# request_body = request.get_json() + +# task.title = request_body["title"] +# task.description = request_body["description"] + +# db.session.commit() + +# return jsonify({"task": task.to_dict()}), 200 + + +# #DELETE /tasks/ +# @tasks_bp.route("", methods=["DELETE"]) +# def delete_task(task_id): +# task = validate_model(Task, task_id) + +# if task is None: +# return {'details': 'Invalid data'}, 404 + +# db.session.delete(task) +# db.session.commit() + +# message = {"details": f"Task 1 \"{task.title}\" successfully deleted"} +# return make_response(message, 200) + +# #Patch //mark_complete +# @tasks_bp.route("/mark_complete", methods=["PATCH"]) +# def mark_task_complete(task_id): +# try: +# new_task = validate_model(Task, task_id) +# except: +# return jsonify({"Message": "Invalid id"}), 404 + +# new_task.completed_at = datetime.utcnow() + +# send_slack_message(new_task) + +# db.session.commit() + +# return jsonify({"task": new_task.to_dict()}), 200 + + +# @tasks_bp.route("/mark_incomplete", methods=["PATCH"]) +# def mark_task_incomplete(task_id): +# try: +# new_task = validate_model(Task, task_id) +# except: +# return jsonify({"Message": "Invalid id"}), 404 + +# new_task.completed_at = None + +# db.session.commit() + +# return jsonify({"task": new_task.to_dict()}), 200 + + + +# def send_slack_message(completed_task): +# TOKEN = os.environ['SLACK_API_TOKEN'] +# AUTH_HEADERS = { +# "Authorization": f"Bearer {TOKEN}" +# } +# CHANNEL_ID = "C0561UUDX4K" +# SLACK_URL = "https://slack.com/api/chat.postMessage" + +# try: +# message = f"Someone just completed the task {completed_task.title}" +# payload = { +# "channel": CHANNEL_ID, +# "text": message +# } + +# requests.post(SLACK_URL, data = payload, headers = AUTH_HEADERS) + +# except: +# print("There was an error making the call to Slack") + + +# #----------------------------------------------------------- + +# @goals_bp.route("", methods=["POST"]) +# def create_goal(): +# try: +# request_body = request.get_json() +# new_goal = Goal.from_dict(request_body) +# except KeyError as err: +# return make_response({"details": "Invalid data"}, 400) + +# db.session.add(new_goal) +# db.session.commit() + +# return jsonify({"goal": new_goal.to_dict()}), 201 + + +# @goals_bp.route("", methods=["GET"]) +# def get_all_goals(): + +# goals = Goal.query.all() + +# goal_response = [] +# for goal in goals: +# goal_response.append(goal.to_dict()) + +# return jsonify(goal_response), 200 + + +# @goals_bp.route("/", methods=["GET"]) +# def get_one_goal(goal_id): +# goal = validate_model(Goal, goal_id) +# if goal: +# return {"goal": goal.to_dict()}, 200 + +# else: +# return {'details': 'Invalid data'}, 404 + + +# @goals_bp.route("", methods=["PUT"]) +# def update_task(goal_id): +# try: +# goal = validate_model(Goal, goal_id) +# except: +# return jsonify({"Message": "Invalid id"}), 404 + +# request_body = request.get_json() + +# goal.title = request_body["title"] + +# db.session.commit() + +# return jsonify({"goal": goal.to_dict()}), 200 + + +# @goals_bp.route("", methods=["DELETE"]) +# def delete_goal(goal_id): +# try: +# goal = validate_model(Goal, goal_id) + +# db.session.delete(goal) +# db.session.commit() + +# message = {"details": f"Goal 1 \"{goal.title}\" successfully deleted"} +# return make_response(message, 200) + +# except: +# return {'details': 'Invalid data'}, 404 + +# #---------------------------------------------------------- + +# @goals_bp.route("//tasks", methods=['POST']) +# def create_goal_with_tasks(goal_id): + +# request_body = request.get_json() +# goal = validate_model(Goal, goal_id) +# task_list = request_body.get("task_ids") + +# tasks = [] +# for task_id in task_list: +# task = validate_model(Task, task_id) +# task.goal = goal +# tasks.append(task_id) + +# db.session.commit() + +# message = { +# "id": goal.goal_id, +# "task_ids": tasks +# } +# return make_response(message, 200) + + +# @goals_bp.route("//tasks", methods=['GET']) +# def get_all_tasks_one_goal(goal_id): +# try: +# goal = validate_model(Goal, goal_id) +# except: +# return make_response({"details": "Invalid data"}, 404) + +# task_list = [] + +# for task in goal.tasks: +# task = validate_model(Task, task.id) +# task_list.append(task.to_dict()) + +# message = { +# "id": goal.goal_id, +# "title": goal.title, +# "tasks": task_list +# } +# return make_response((message, 200)) \ No newline at end of file diff --git a/app/routes/task_routes.py b/app/routes/task_routes.py new file mode 100644 index 000000000..02beeed5b --- /dev/null +++ b/app/routes/task_routes.py @@ -0,0 +1,157 @@ +from flask import Blueprint, jsonify, abort, make_response, request +import requests +from app.models.task import Task +from app.routes.helpers import validate_model +from datetime import datetime +from app import db +import os + +tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") + +#POST /tasks +@tasks_bp.route("", methods=["POST"]) +def create_task(): + try: + request_body = request.get_json() + new_task = Task.from_dict(request_body) + except KeyError as err: + return make_response({"details": "Invalid data"}, 400) + + db.session.add(new_task) + db.session.commit() + + return jsonify({"task": new_task.to_dict()}), 201 + + +#Validate Model +def validate_model(cls, task_id): + try: + task_id = int(task_id) + except: + abort(make_response({"details": "Invalid data"}, 400)) + + task = cls.query.get(task_id) + + if not task: + message = {"details": "Invalid data"} + abort(make_response(message, 404)) + + return task + + +#GET /tasks +@tasks_bp.route("", methods=["GET"]) +def get_all_tasks(): + + sort_query = request.args.get("sort") + + if sort_query == "asc": + tasks = Task.query.order_by(Task.title) + + elif sort_query == "desc": + tasks = Task.query.order_by(Task.title.desc()) + + else: + tasks = Task.query.all() + + tasks_response = [] + for task in tasks: + tasks_response.append(task.to_dict()) + + return jsonify(tasks_response), 200 + + +#GET /tasks/ +@tasks_bp.route("/", methods=["GET"]) +def get_one_task(task_id): + task = validate_model(Task, task_id) + if task: + return {"task": task.to_dict()}, 200 + + else: + return {'details': 'Invalid data'}, 404 + + +#PUT /tasks/ +@tasks_bp.route("", methods=["PUT"]) +def update_task(task_id): + try: + task = validate_model(Task, task_id) + except: + return jsonify({"Message": "Invalid id"}), 404 + + request_body = request.get_json() + + task.title = request_body["title"] + task.description = request_body["description"] + + db.session.commit() + + return jsonify({"task": task.to_dict()}), 200 + + +#DELETE /tasks/ +@tasks_bp.route("", methods=["DELETE"]) +def delete_task(task_id): + task = validate_model(Task, task_id) + + if task is None: + return {'details': 'Invalid data'}, 404 + + db.session.delete(task) + db.session.commit() + + message = {"details": f"Task 1 \"{task.title}\" successfully deleted"} + return make_response(message, 200) + +#Patch //mark_complete +@tasks_bp.route("/mark_complete", methods=["PATCH"]) +def mark_task_complete(task_id): + try: + new_task = validate_model(Task, task_id) + except: + return jsonify({"Message": "Invalid id"}), 404 + + new_task.completed_at = datetime.utcnow() + + send_slack_message(new_task) + + db.session.commit() + + return jsonify({"task": new_task.to_dict()}), 200 + + +@tasks_bp.route("/mark_incomplete", methods=["PATCH"]) +def mark_task_incomplete(task_id): + try: + new_task = validate_model(Task, task_id) + except: + return jsonify({"Message": "Invalid id"}), 404 + + new_task.completed_at = None + + db.session.commit() + + return jsonify({"task": new_task.to_dict()}), 200 + + + +def send_slack_message(completed_task): + TOKEN = os.environ['SLACK_API_TOKEN'] + AUTH_HEADERS = { + "Authorization": f"Bearer {TOKEN}" + } + CHANNEL_ID = "C0561UUDX4K" + SLACK_URL = "https://slack.com/api/chat.postMessage" + + try: + message = f"Someone just completed the task {completed_task.title}" + payload = { + "channel": CHANNEL_ID, + "text": message + } + + requests.post(SLACK_URL, data = payload, headers = AUTH_HEADERS) + + except: + print("There was an error making the call to Slack") \ No newline at end of file From 7868e6118400545f153f4435b5d7dfb999aed8c9 Mon Sep 17 00:00:00 2001 From: Whitney Date: Sun, 14 May 2023 15:26:27 -0700 Subject: [PATCH 15/20] Working through refactor, issue writing cls update function --- app/models/goal.py | 40 +++++++++++++-------- app/models/task.py | 18 +++++----- app/routes/goal_routes.py | 46 +++++++----------------- app/routes/helpers.py | 75 ++++++++++++++++++++++++++++++++++++--- app/routes/task_routes.py | 74 ++++++++------------------------------ tests/test_wave_01.py | 20 +++-------- tests/test_wave_05.py | 18 +++------- 7 files changed, 142 insertions(+), 149 deletions(-) diff --git a/app/models/goal.py b/app/models/goal.py index a96c5d4a3..5d94d8934 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -15,25 +15,35 @@ def from_dict(cls, goal_data): new_goal = cls(title=goal_data["title"]) return new_goal - def to_dict(self): + def to_dict(self, tasks=False): + + if tasks: + tasks_list = [] + for task in self.tasks: + tasks_list.append(task.to_dict()) + return dict( + id = self.goal_id, + title = self.title, + tasks = tasks_list + ) return dict( id = self.goal_id, title = self.title) # from app.models.task import Task - def connected_dict(self, task_data): - return dict( - id = self.goal_id, - title = self.title, - tasks = [ - dict( - id = task_data["id"], - goal_id = self.goal_id, - title = task_data["title"], - description = task_data["description"], - completed_at = task_data["completed_at"] - ) - ] - ) + # def connected_dict(self, task_data): + # return dict( + # id = self.goal_id, + # title = self.title, + # tasks = [ + # dict( + # id = task_data["id"], + # goal_id = self.goal_id, + # title = task_data["title"], + # description = task_data["description"], + # completed_at = task_data["completed_at"] + # ) + # ] + # ) \ No newline at end of file diff --git a/app/models/task.py b/app/models/task.py index 969abb921..1f0de1b75 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -46,15 +46,15 @@ def is_task_complete(self): else: return True - from app.models.goal import Goal - def to_goal_id_dict(self, goal_id): - return dict( - id = self.id, - goal_id = goal_id, - title = self.title, - description = self.description, - is_complete = self.is_task_complete() - ) + # from app.models.goal import Goal + # def to_goal_id_dict(self, goal_id): + # return dict( + # id = self.id, + # goal_id = goal_id, + # title = self.title, + # description = self.description, + # is_complete = self.is_task_complete() + # ) # from app.models.goal import Goal # def to_dict_with_goal(self, goal_id): # return dict( diff --git a/app/routes/goal_routes.py b/app/routes/goal_routes.py index bed1cc9be..31cb899f2 100644 --- a/app/routes/goal_routes.py +++ b/app/routes/goal_routes.py @@ -2,7 +2,7 @@ import requests from app.models.goal import Goal from app.models.task import Task -from app.routes.helpers import validate_model +from app.routes.helpers import validate_model, create_item, get_all_items, get_one_item, update_item from datetime import datetime from app import db import os @@ -11,54 +11,34 @@ @goals_bp.route("", methods=["POST"]) def create_goal(): - try: - request_body = request.get_json() - new_goal = Goal.from_dict(request_body) - except KeyError as err: - return make_response({"details": "Invalid data"}, 400) - - db.session.add(new_goal) - db.session.commit() - - return jsonify({"goal": new_goal.to_dict()}), 201 + return create_item(Goal) @goals_bp.route("", methods=["GET"]) def get_all_goals(): - - goals = Goal.query.all() - - goal_response = [] - for goal in goals: - goal_response.append(goal.to_dict()) - - return jsonify(goal_response), 200 + return get_all_items(Goal) @goals_bp.route("/", methods=["GET"]) def get_one_goal(goal_id): - goal = validate_model(Goal, goal_id) - if goal: - return {"goal": goal.to_dict()}, 200 - - else: - return {'details': 'Invalid data'}, 404 + return get_one_item(Goal, goal_id) @goals_bp.route("", methods=["PUT"]) def update_task(goal_id): - try: - goal = validate_model(Goal, goal_id) - except: - return jsonify({"Message": "Invalid id"}), 404 + return update_item(Goal, goal_id) + # try: + # goal = validate_model(Goal, goal_id) + # except: + # return jsonify({"Message": "Invalid id"}), 404 - request_body = request.get_json() + # request_body = request.get_json() - goal.title = request_body["title"] + # goal.title = request_body["title"] - db.session.commit() + # db.session.commit() - return jsonify({"goal": goal.to_dict()}), 200 + # return jsonify({"goal": goal.to_dict()}), 200 @goals_bp.route("", methods=["DELETE"]) diff --git a/app/routes/helpers.py b/app/routes/helpers.py index 8ef758c19..e19fd066e 100644 --- a/app/routes/helpers.py +++ b/app/routes/helpers.py @@ -7,16 +7,81 @@ import os -def validate_model(cls, task_id): +def validate_model(cls, model_id): try: - task_id = int(task_id) + model_id = int(model_id) except: abort(make_response({"details": "Invalid data"}, 400)) - task = cls.query.get(task_id) + model = cls.query.get(model_id) - if not task: + if not model: message = {"details": "Invalid data"} abort(make_response(message, 404)) - return task \ No newline at end of file + return model + +def create_item(cls): + try: + request_body = request.get_json() + new_item = cls.from_dict(request_body) + except KeyError as err: + return make_response({"details": "Invalid data"}, 400) + + db.session.add(new_item) + db.session.commit() + + return make_response({cls.__name__.lower(): new_item.to_dict()}, 201) + + +def get_all_items(cls): + + sort_query = request.args.get("sort") + + if sort_query == "asc": + items = cls.query.order_by(cls.title) + + elif sort_query == "desc": + items = cls.query.order_by(cls.title.desc()) + + else: + items = cls.query.all() + + if request.args.get("title"): + items = cls.query.filter(cls.title== request.args.get("title")) + + items_response = [] + for item in items: + items_response.append(item.to_dict()) + + return jsonify(items_response), 200 + + +def get_one_item(cls, model_id): + + item = validate_model(cls, model_id) + + if item: + return make_response({cls.__name__.lower(): item.to_dict()}), 200 + + else: + return {'details': 'Invalid data'}, 404 + + + +def update_item(cls, model_id): + + item = validate_model(cls, model_id) + request_body = request.get_json() + + # return jsonify({"Message": "Invalid id"}, 404) + + for key, value in request_body.items(): + setattr(item, key, value) + + db.session.commit() + + return jsonify({cls.__name__.lower(): item.to_dict()}, 200) + +#-------------------------------------------- + diff --git a/app/routes/task_routes.py b/app/routes/task_routes.py index 02beeed5b..2da5f74e4 100644 --- a/app/routes/task_routes.py +++ b/app/routes/task_routes.py @@ -1,7 +1,7 @@ from flask import Blueprint, jsonify, abort, make_response, request import requests from app.models.task import Task -from app.routes.helpers import validate_model +from app.routes.helpers import validate_model, create_item, get_all_items, get_one_item, update_item from datetime import datetime from app import db import os @@ -11,83 +11,39 @@ #POST /tasks @tasks_bp.route("", methods=["POST"]) def create_task(): - try: - request_body = request.get_json() - new_task = Task.from_dict(request_body) - except KeyError as err: - return make_response({"details": "Invalid data"}, 400) - - db.session.add(new_task) - db.session.commit() - - return jsonify({"task": new_task.to_dict()}), 201 + return create_item(Task) -#Validate Model -def validate_model(cls, task_id): - try: - task_id = int(task_id) - except: - abort(make_response({"details": "Invalid data"}, 400)) - - task = cls.query.get(task_id) - - if not task: - message = {"details": "Invalid data"} - abort(make_response(message, 404)) - - return task - #GET /tasks @tasks_bp.route("", methods=["GET"]) def get_all_tasks(): - - sort_query = request.args.get("sort") - - if sort_query == "asc": - tasks = Task.query.order_by(Task.title) - - elif sort_query == "desc": - tasks = Task.query.order_by(Task.title.desc()) - - else: - tasks = Task.query.all() - - tasks_response = [] - for task in tasks: - tasks_response.append(task.to_dict()) - - return jsonify(tasks_response), 200 + return get_all_items(Task) #GET /tasks/ @tasks_bp.route("/", methods=["GET"]) def get_one_task(task_id): - task = validate_model(Task, task_id) - if task: - return {"task": task.to_dict()}, 200 - - else: - return {'details': 'Invalid data'}, 404 - + return get_one_item(Task, task_id) + #PUT /tasks/ @tasks_bp.route("", methods=["PUT"]) def update_task(task_id): - try: - task = validate_model(Task, task_id) - except: - return jsonify({"Message": "Invalid id"}), 404 + return update_item(Task, task_id) + # try: + # task = validate_model(Task, task_id) + # except: + # return jsonify({"Message": "Invalid id"}), 404 - request_body = request.get_json() + # 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"] - db.session.commit() + # db.session.commit() - return jsonify({"task": task.to_dict()}), 200 + # return jsonify({"task": task.to_dict()}), 200 #DELETE /tasks/ diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index a81f8bc40..dca9a8dbb 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -5,7 +5,7 @@ # print(f"{response = }") # print(f"{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_no_saved_tasks(client): # Act response = client.get("/tasks") @@ -16,7 +16,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") @@ -35,7 +35,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") @@ -53,7 +53,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") @@ -63,13 +63,8 @@ def test_get_task_not_found(client): assert response.status_code == 404 assert response_body == {'details': 'Invalid data'} - # 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={ @@ -166,11 +161,6 @@ def test_delete_task_not_found(client): assert response.status_code == 404 assert response_body == {'details': 'Invalid data'} - # raise Exception("Complete test with assertion about response body") - # ***************************************************************** - # **Complete test with assertion about response body*************** - # ***************************************************************** - assert Task.query.all() == [] diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index f287d40bb..d2953acbe 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.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_goals_no_saved_goals(client): # Act response = client.get("/goals") @@ -13,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") @@ -30,7 +30,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") @@ -47,9 +47,8 @@ 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() @@ -57,15 +56,8 @@ def test_get_goal_not_found(client): response.status_code == 404 response_body == {'details': 'Invalid data'} - # raise Exception("Complete test") - # Assert - # ---- Complete Test ---- - # assertion 1 goes here - # assertion 2 goes here - # ---- 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={ From bbf5dc738e145028eebb15503ceb8fc8720ad4bf Mon Sep 17 00:00:00 2001 From: Whitney Date: Sun, 14 May 2023 17:55:08 -0700 Subject: [PATCH 16/20] All waves passing, all files complete, Postman not retrieving data --- app/models/goal.py | 41 ++------ app/models/task.py | 31 ++---- app/routes/goal_routes.py | 36 +------ app/routes/helpers.py | 97 ++++++++++++++----- app/routes/task_routes.py | 85 ++-------------- .../101ccdb3ffd0_changed_id_to_task_id.py | 42 ++++++++ tests/test_wave_01.py | 20 ++-- tests/test_wave_05.py | 10 +- 8 files changed, 154 insertions(+), 208 deletions(-) create mode 100644 migrations/versions/101ccdb3ffd0_changed_id_to_task_id.py diff --git a/app/models/goal.py b/app/models/goal.py index 5d94d8934..bafba3f49 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -6,9 +6,7 @@ class Goal(db.Model): goal_id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) # tasks = db.relationship("Task",backref="goal", lazy = True) - tasks = db.relationship("Task", back_populates="goal") - - # tasks = db.relationship("Task", back_populates="goal", lazy = True) + tasks = db.relationship("Task", back_populates="goal", lazy=True) @classmethod def from_dict(cls, goal_data): @@ -17,33 +15,12 @@ def from_dict(cls, goal_data): def to_dict(self, tasks=False): + build_dict = { + "id": self.goal_id, + "title": self.title + } + if tasks: - tasks_list = [] - for task in self.tasks: - tasks_list.append(task.to_dict()) - return dict( - id = self.goal_id, - title = self.title, - tasks = tasks_list - ) - return dict( - id = self.goal_id, - title = self.title) - - # from app.models.task import Task - - # def connected_dict(self, task_data): - # return dict( - # id = self.goal_id, - # title = self.title, - # tasks = [ - # dict( - # id = task_data["id"], - # goal_id = self.goal_id, - # title = task_data["title"], - # description = task_data["description"], - # completed_at = task_data["completed_at"] - # ) - # ] - # ) - \ No newline at end of file + build_dict["tasks"] = [task.to_dict() for task in self.tasks] + + return build_dict diff --git a/app/models/task.py b/app/models/task.py index 1f0de1b75..2023e86c7 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -4,9 +4,9 @@ class Task(db.Model): - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - title = db.Column(db.String) # nullable=False) - description = db.Column(db.String) #nullable=False) + task_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + title = db.Column(db.String, nullable=False) + description = db.Column(db.String, nullable=False) # completed_at = db.Column(db.DateTime, default = None) completed_at = db.Column(db.DateTime(timezone=True), nullable=True) goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True) @@ -26,7 +26,7 @@ def to_dict(self): if self.goal_id: return dict( - id = self.id, + id = self.task_id, goal_id = self.goal_id, title = self.title, description = self.description, @@ -34,7 +34,7 @@ def to_dict(self): ) return dict( - id = self.id, + id = self.task_id, title = self.title, description = self.description, is_complete = self.is_task_complete() @@ -46,24 +46,5 @@ def is_task_complete(self): else: return True - # from app.models.goal import Goal - # def to_goal_id_dict(self, goal_id): - # return dict( - # id = self.id, - # goal_id = goal_id, - # title = self.title, - # description = self.description, - # is_complete = self.is_task_complete() - # ) - # from app.models.goal import Goal - # def to_dict_with_goal(self, goal_id): - # return dict( - # id = self.id, - # goal_id = int(goal_id), - # title = self.title, - # description = self.description, - # is_complete = self.is_task_complete() - # ) - - + diff --git a/app/routes/goal_routes.py b/app/routes/goal_routes.py index 31cb899f2..e60fe3fa7 100644 --- a/app/routes/goal_routes.py +++ b/app/routes/goal_routes.py @@ -1,11 +1,8 @@ -from flask import Blueprint, jsonify, abort, make_response, request -import requests +from flask import Blueprint, make_response, request from app.models.goal import Goal from app.models.task import Task -from app.routes.helpers import validate_model, create_item, get_all_items, get_one_item, update_item -from datetime import datetime +from app.routes.helpers import validate_model, create_item, get_all_items, get_one_item, update_item, delete_item from app import db -import os goals_bp = Blueprint("goals", __name__, url_prefix="/goals") @@ -27,35 +24,12 @@ def get_one_goal(goal_id): @goals_bp.route("", methods=["PUT"]) def update_task(goal_id): return update_item(Goal, goal_id) - # try: - # goal = validate_model(Goal, goal_id) - # except: - # return jsonify({"Message": "Invalid id"}), 404 - - # request_body = request.get_json() - - # goal.title = request_body["title"] - - # db.session.commit() - - # return jsonify({"goal": goal.to_dict()}), 200 - + @goals_bp.route("", methods=["DELETE"]) def delete_goal(goal_id): - try: - goal = validate_model(Goal, goal_id) - - db.session.delete(goal) - db.session.commit() - - message = {"details": f"Goal 1 \"{goal.title}\" successfully deleted"} - return make_response(message, 200) - - except: - return {'details': 'Invalid data'}, 404 + return delete_item(Goal, goal_id) -#---------------------------------------------------------- @goals_bp.route("//tasks", methods=['POST']) def create_goal_with_tasks(goal_id): @@ -89,7 +63,7 @@ def get_all_tasks_one_goal(goal_id): task_list = [] for task in goal.tasks: - task = validate_model(Task, task.id) + task = validate_model(Task, task.task_id) task_list.append(task.to_dict()) message = { diff --git a/app/routes/helpers.py b/app/routes/helpers.py index e19fd066e..9d54a57d7 100644 --- a/app/routes/helpers.py +++ b/app/routes/helpers.py @@ -1,11 +1,29 @@ -from flask import Blueprint, jsonify, abort, make_response, request +from flask import jsonify, abort, make_response, request import requests -from app.models.goal import Goal -from app.models.task import Task from datetime import datetime from app import db import os +def send_slack_message(completed_task): + TOKEN = os.environ['SLACK_API_TOKEN'] + AUTH_HEADERS = { + "Authorization": f"Bearer {TOKEN}" + } + CHANNEL_ID = "C0561UUDX4K" + SLACK_URL = "https://slack.com/api/chat.postMessage" + + try: + message = f"Someone just completed the task {completed_task.title}" + payload = { + "channel": CHANNEL_ID, + "text": message + } + + requests.post(SLACK_URL, data = payload, headers = AUTH_HEADERS) + + except: + print("There was an error making the call to Slack") + def validate_model(cls, model_id): try: @@ -21,17 +39,19 @@ def validate_model(cls, model_id): return model + def create_item(cls): + + request_body = request.get_json() + try: - request_body = request.get_json() new_item = cls.from_dict(request_body) - except KeyError as err: + db.session.add(new_item) + db.session.commit() + return make_response({cls.__name__.lower(): new_item.to_dict()}, 201) + except: return make_response({"details": "Invalid data"}, 400) - db.session.add(new_item) - db.session.commit() - - return make_response({cls.__name__.lower(): new_item.to_dict()}, 201) def get_all_items(cls): @@ -60,28 +80,61 @@ def get_all_items(cls): def get_one_item(cls, model_id): item = validate_model(cls, model_id) + return make_response({cls.__name__.lower(): item.to_dict()}), 200 + - if item: - return make_response({cls.__name__.lower(): item.to_dict()}), 200 +def update_item(cls, model_id): + try: + item = validate_model(cls, model_id) + request_body = request.get_json() + + for key, value in request_body.items(): + setattr(item, key, value) + + db.session.commit() + + return make_response({cls.__name__.lower(): item.to_dict()}, 200) - else: - return {'details': 'Invalid data'}, 404 + except: + return jsonify({"Message": "Invalid id"}), 404 +def delete_item(cls, model_id): -def update_item(cls, model_id): - item = validate_model(cls, model_id) - request_body = request.get_json() - - # return jsonify({"Message": "Invalid id"}, 404) + + db.session.delete(item) + db.session.commit() + + message = {"details": f"{cls.__name__} {model_id} \"{item.title}\" successfully deleted"} + return make_response(message, 200) + + +def mark_item_complete(cls, model_id): + try: + new_item = validate_model(cls, model_id) + except: + return jsonify({"Message": "Invalid id"}), 404 - for key, value in request_body.items(): - setattr(item, key, value) + new_item.completed_at = datetime.utcnow() + + send_slack_message(new_item) db.session.commit() - return jsonify({cls.__name__.lower(): item.to_dict()}, 200) + return jsonify({"task": new_item.to_dict()}), 200 + + +def mark_item_incomplete(cls, model_id): + + try: + new_item = validate_model(cls, model_id) + except: + return jsonify({"Message": "Invalid id"}), 404 + + new_item.completed_at = None + + db.session.commit() -#-------------------------------------------- + return jsonify({"task": new_item.to_dict()}), 200 diff --git a/app/routes/task_routes.py b/app/routes/task_routes.py index 2da5f74e4..5a917f01d 100644 --- a/app/routes/task_routes.py +++ b/app/routes/task_routes.py @@ -1,113 +1,40 @@ -from flask import Blueprint, jsonify, abort, make_response, request -import requests +from flask import Blueprint from app.models.task import Task -from app.routes.helpers import validate_model, create_item, get_all_items, get_one_item, update_item -from datetime import datetime -from app import db -import os +from app.routes.helpers import create_item, get_all_items, get_one_item, update_item, delete_item, mark_item_complete, mark_item_incomplete tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") -#POST /tasks + @tasks_bp.route("", methods=["POST"]) def create_task(): return create_item(Task) - -#GET /tasks @tasks_bp.route("", methods=["GET"]) def get_all_tasks(): return get_all_items(Task) -#GET /tasks/ @tasks_bp.route("/", methods=["GET"]) def get_one_task(task_id): return get_one_item(Task, task_id) -#PUT /tasks/ @tasks_bp.route("", methods=["PUT"]) def update_task(task_id): return update_item(Task, task_id) - # try: - # task = validate_model(Task, task_id) - # except: - # return jsonify({"Message": "Invalid id"}), 404 - - # request_body = request.get_json() - - # task.title = request_body["title"] - # task.description = request_body["description"] - - # db.session.commit() - # return jsonify({"task": task.to_dict()}), 200 - -#DELETE /tasks/ @tasks_bp.route("", methods=["DELETE"]) def delete_task(task_id): - task = validate_model(Task, task_id) - - if task is None: - return {'details': 'Invalid data'}, 404 + return delete_item(Task, task_id) - db.session.delete(task) - db.session.commit() - message = {"details": f"Task 1 \"{task.title}\" successfully deleted"} - return make_response(message, 200) - -#Patch //mark_complete @tasks_bp.route("/mark_complete", methods=["PATCH"]) def mark_task_complete(task_id): - try: - new_task = validate_model(Task, task_id) - except: - return jsonify({"Message": "Invalid id"}), 404 - - new_task.completed_at = datetime.utcnow() - - send_slack_message(new_task) - - db.session.commit() - - return jsonify({"task": new_task.to_dict()}), 200 + return mark_item_complete(Task, task_id) @tasks_bp.route("/mark_incomplete", methods=["PATCH"]) def mark_task_incomplete(task_id): - try: - new_task = validate_model(Task, task_id) - except: - return jsonify({"Message": "Invalid id"}), 404 - - new_task.completed_at = None - - db.session.commit() - - return jsonify({"task": new_task.to_dict()}), 200 - - - -def send_slack_message(completed_task): - TOKEN = os.environ['SLACK_API_TOKEN'] - AUTH_HEADERS = { - "Authorization": f"Bearer {TOKEN}" - } - CHANNEL_ID = "C0561UUDX4K" - SLACK_URL = "https://slack.com/api/chat.postMessage" - - try: - message = f"Someone just completed the task {completed_task.title}" - payload = { - "channel": CHANNEL_ID, - "text": message - } - - requests.post(SLACK_URL, data = payload, headers = AUTH_HEADERS) - - except: - print("There was an error making the call to Slack") \ No newline at end of file + return mark_item_incomplete(Task, task_id) \ No newline at end of file diff --git a/migrations/versions/101ccdb3ffd0_changed_id_to_task_id.py b/migrations/versions/101ccdb3ffd0_changed_id_to_task_id.py new file mode 100644 index 000000000..ae20ef227 --- /dev/null +++ b/migrations/versions/101ccdb3ffd0_changed_id_to_task_id.py @@ -0,0 +1,42 @@ +"""changed id to task_id + +Revision ID: 101ccdb3ffd0 +Revises: 34b6ef6a0a86 +Create Date: 2023-05-14 16:07:40.214718 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '101ccdb3ffd0' +down_revision = '34b6ef6a0a86' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('task_id', sa.Integer(), autoincrement=True, nullable=False)) + op.alter_column('task', 'description', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('task', 'title', + existing_type=sa.VARCHAR(), + nullable=True) + op.drop_column('task', 'id') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False)) + op.alter_column('task', 'title', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('task', 'description', + existing_type=sa.VARCHAR(), + nullable=False) + op.drop_column('task', 'task_id') + # ### end Alembic commands ### diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index dca9a8dbb..7f2c23280 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -2,10 +2,7 @@ import pytest -# print(f"{response = }") - # print(f"{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_no_saved_tasks(client): # Act response = client.get("/tasks") @@ -16,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") @@ -33,9 +30,9 @@ def test_get_tasks_one_saved_tasks(client, one_task): "is_complete": False } ] + - -@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") @@ -53,7 +50,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") @@ -64,7 +61,7 @@ def test_get_task_not_found(client): assert response_body == {'details': 'Invalid data'} -@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={ @@ -130,11 +127,6 @@ def test_update_task_not_found(client): assert response.status_code == 404 assert response_body == {"Message": "Invalid id"}, 404 - # 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") def test_delete_task(client, one_task): diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index d2953acbe..d82aa1736 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.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_goals_no_saved_goals(client): # Act response = client.get("/goals") @@ -13,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") @@ -30,7 +30,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") @@ -47,7 +47,7 @@ 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): # Act response = client.get("/goals/1") @@ -57,7 +57,7 @@ def test_get_goal_not_found(client): response_body == {'details': 'Invalid data'} -@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={ From 5cd753c2751a03324ad0d5aba95b3880af302c11 Mon Sep 17 00:00:00 2001 From: Whitney Date: Sun, 14 May 2023 18:03:43 -0700 Subject: [PATCH 17/20] Dropped databases and created new ones --- app/models/goal.py | 1 - app/models/task.py | 4 -- migrations/env.py | 4 -- .../101ccdb3ffd0_changed_id_to_task_id.py | 42 ------------------- ...6a0a86_created_one_to_many_relationship.py | 30 ------------- ...b_dropped_prior_databases_creating_new.py} | 17 ++++---- .../6cc23de38d65_initial_migration.py | 28 ------------- 7 files changed, 10 insertions(+), 116 deletions(-) delete mode 100644 migrations/versions/101ccdb3ffd0_changed_id_to_task_id.py delete mode 100644 migrations/versions/34b6ef6a0a86_created_one_to_many_relationship.py rename migrations/versions/{a91689b1b85e_recreating_databases_again.py => 5c1b99b6d55b_dropped_prior_databases_creating_new.py} (59%) delete mode 100644 migrations/versions/6cc23de38d65_initial_migration.py diff --git a/app/models/goal.py b/app/models/goal.py index bafba3f49..d5a8609e6 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -5,7 +5,6 @@ class Goal(db.Model): goal_id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) - # tasks = db.relationship("Task",backref="goal", lazy = True) tasks = db.relationship("Task", back_populates="goal", lazy=True) @classmethod diff --git a/app/models/task.py b/app/models/task.py index 2023e86c7..e15808676 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -1,19 +1,15 @@ from app import db -import datetime - class Task(db.Model): task_id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String, nullable=False) description = db.Column(db.String, nullable=False) - # completed_at = db.Column(db.DateTime, default = None) completed_at = db.Column(db.DateTime(timezone=True), nullable=True) goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True) goal = db.relationship("Goal", back_populates="tasks") - @classmethod def from_dict(cls, task_data): new_task = cls(title=task_data["title"], diff --git a/migrations/env.py b/migrations/env.py index 1c870e77a..8b3fb3353 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -94,7 +94,3 @@ def process_revision_directives(context, revision, directives): run_migrations_offline() else: run_migrations_online() - - - - diff --git a/migrations/versions/101ccdb3ffd0_changed_id_to_task_id.py b/migrations/versions/101ccdb3ffd0_changed_id_to_task_id.py deleted file mode 100644 index ae20ef227..000000000 --- a/migrations/versions/101ccdb3ffd0_changed_id_to_task_id.py +++ /dev/null @@ -1,42 +0,0 @@ -"""changed id to task_id - -Revision ID: 101ccdb3ffd0 -Revises: 34b6ef6a0a86 -Create Date: 2023-05-14 16:07:40.214718 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '101ccdb3ffd0' -down_revision = '34b6ef6a0a86' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('task_id', sa.Integer(), autoincrement=True, nullable=False)) - op.alter_column('task', 'description', - existing_type=sa.VARCHAR(), - nullable=True) - op.alter_column('task', 'title', - existing_type=sa.VARCHAR(), - nullable=True) - op.drop_column('task', 'id') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False)) - op.alter_column('task', 'title', - existing_type=sa.VARCHAR(), - nullable=False) - op.alter_column('task', 'description', - existing_type=sa.VARCHAR(), - nullable=False) - op.drop_column('task', 'task_id') - # ### end Alembic commands ### diff --git a/migrations/versions/34b6ef6a0a86_created_one_to_many_relationship.py b/migrations/versions/34b6ef6a0a86_created_one_to_many_relationship.py deleted file mode 100644 index 760f98b90..000000000 --- a/migrations/versions/34b6ef6a0a86_created_one_to_many_relationship.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Created one to many relationship - -Revision ID: 34b6ef6a0a86 -Revises: 6cc23de38d65 -Create Date: 2023-05-11 21:04:32.850059 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '34b6ef6a0a86' -down_revision = '6cc23de38d65' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('task', sa.Column('goal_id', sa.Integer(), nullable=True)) - op.create_foreign_key(None, 'task', 'goal', ['goal_id'], ['goal_id']) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'task', type_='foreignkey') - op.drop_column('task', 'goal_id') - # ### end Alembic commands ### diff --git a/migrations/versions/a91689b1b85e_recreating_databases_again.py b/migrations/versions/5c1b99b6d55b_dropped_prior_databases_creating_new.py similarity index 59% rename from migrations/versions/a91689b1b85e_recreating_databases_again.py rename to migrations/versions/5c1b99b6d55b_dropped_prior_databases_creating_new.py index 573cf3255..a03e5144a 100644 --- a/migrations/versions/a91689b1b85e_recreating_databases_again.py +++ b/migrations/versions/5c1b99b6d55b_dropped_prior_databases_creating_new.py @@ -1,8 +1,8 @@ -"""recreating databases AGAIN +"""Dropped prior databases, creating new -Revision ID: a91689b1b85e +Revision ID: 5c1b99b6d55b Revises: -Create Date: 2023-05-09 22:27:41.009158 +Create Date: 2023-05-14 18:02:04.127203 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = 'a91689b1b85e' +revision = '5c1b99b6d55b' down_revision = None branch_labels = None depends_on = None @@ -20,14 +20,17 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.create_table('goal', sa.Column('goal_id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(), nullable=True), sa.PrimaryKeyConstraint('goal_id') ) op.create_table('task', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('task_id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('title', sa.String(), nullable=False), sa.Column('description', sa.String(), nullable=False), - sa.Column('completed_at', sa.DateTime(), nullable=True), - sa.PrimaryKeyConstraint('id') + sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True), + sa.Column('goal_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['goal_id'], ['goal.goal_id'], ), + sa.PrimaryKeyConstraint('task_id') ) # ### end Alembic commands ### diff --git a/migrations/versions/6cc23de38d65_initial_migration.py b/migrations/versions/6cc23de38d65_initial_migration.py deleted file mode 100644 index b608daf23..000000000 --- a/migrations/versions/6cc23de38d65_initial_migration.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Initial Migration - -Revision ID: 6cc23de38d65 -Revises: a91689b1b85e -Create Date: 2023-05-11 16:30:36.845767 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '6cc23de38d65' -down_revision = 'a91689b1b85e' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('goal', sa.Column('title', sa.String(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('goal', 'title') - # ### end Alembic commands ### From e15bd80f6ec4ed0c273e9b24f05e1ece4f4cc23e Mon Sep 17 00:00:00 2001 From: Whitney Date: Sun, 14 May 2023 20:36:38 -0700 Subject: [PATCH 18/20] All waves passing, all postman requests perfect EXCEPT for the ones for goals/1/tasks --- app/models/goal.py | 16 ++++--- app/models/task.py | 34 +++++++------- app/routes/goal_routes.py | 45 ++++++++++--------- app/routes/helpers.py | 4 ++ app/routes/task_routes.py | 1 + ...9e_removed_nullables_to_try_to_correct_.py | 38 ++++++++++++++++ tests/test_wave_06.py | 5 --- 7 files changed, 93 insertions(+), 50 deletions(-) create mode 100644 migrations/versions/a5c35fb18f9e_removed_nullables_to_try_to_correct_.py diff --git a/app/models/goal.py b/app/models/goal.py index d5a8609e6..c44aded19 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -7,11 +7,6 @@ class Goal(db.Model): title = db.Column(db.String) tasks = db.relationship("Task", back_populates="goal", lazy=True) - @classmethod - def from_dict(cls, goal_data): - new_goal = cls(title=goal_data["title"]) - return new_goal - def to_dict(self, tasks=False): build_dict = { @@ -23,3 +18,14 @@ def to_dict(self, tasks=False): build_dict["tasks"] = [task.to_dict() for task in self.tasks] return build_dict + + @classmethod + def from_dict(cls, build_dict): + new_goal = cls(title=build_dict["title"]) + return new_goal + + # def from_dict(cls, build_dict): + # return Goal( + # title = build_dict["title"] + # ) + diff --git a/app/models/task.py b/app/models/task.py index e15808676..c2ac371c6 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -3,44 +3,40 @@ class Task(db.Model): task_id = db.Column(db.Integer, primary_key=True, autoincrement=True) - title = db.Column(db.String, nullable=False) - description = db.Column(db.String, nullable=False) + title = db.Column(db.String) + description = db.Column(db.String) completed_at = db.Column(db.DateTime(timezone=True), nullable=True) goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True) goal = db.relationship("Goal", back_populates="tasks") - - - @classmethod - def from_dict(cls, task_data): - new_task = cls(title=task_data["title"], - description=task_data["description"], - completed_at=None) - return new_task def to_dict(self): - if self.goal_id: - return dict( + task_dict = dict( id = self.task_id, - goal_id = self.goal_id, title = self.title, description = self.description, is_complete = self.is_task_complete() ) - return dict( - id = self.task_id, - title = self.title, - description = self.description, - is_complete = self.is_task_complete() - ) + if self.goal_id: + task_dict["goal_id"] = self.goal_id + + return task_dict def is_task_complete(self): if self.completed_at == None: return False else: return True + + @classmethod + def from_dict(cls, task_dict): + new_task = Task( + title=task_dict["title"], + description=task_dict["description"], + ) + return new_task diff --git a/app/routes/goal_routes.py b/app/routes/goal_routes.py index e60fe3fa7..0bfac5c7d 100644 --- a/app/routes/goal_routes.py +++ b/app/routes/goal_routes.py @@ -34,41 +34,44 @@ def delete_goal(goal_id): @goals_bp.route("//tasks", methods=['POST']) def create_goal_with_tasks(goal_id): - request_body = request.get_json() goal = validate_model(Goal, goal_id) - task_list = request_body.get("task_ids") + request_body = request.get_json() + task_ids = request_body.get("task_ids") - tasks = [] - for task_id in task_list: + # for task in goal.tasks: + # task.goal_id = None + + for task_id in task_ids: task = validate_model(Task, task_id) - task.goal = goal - tasks.append(task_id) + task.goal_id = goal.goal_id + + # goal.title = request.args.get("title", goal.title) db.session.commit() message = { "id": goal.goal_id, - "task_ids": tasks + "task_ids": task_ids } + return make_response(message, 200) @goals_bp.route("//tasks", methods=['GET']) def get_all_tasks_one_goal(goal_id): - try: - goal = validate_model(Goal, goal_id) - except: - return make_response({"details": "Invalid data"}, 404) - task_list = [] + goal = validate_model(Goal, goal_id) + return make_response(goal.to_dict(tasks=True), 200) + + # task_list = [] - for task in goal.tasks: - task = validate_model(Task, task.task_id) - task_list.append(task.to_dict()) + # for task in goal.tasks: + # task = validate_model(Task, task.task_id) + # task_list.append(task.to_dict()) - message = { - "id": goal.goal_id, - "title": goal.title, - "tasks": task_list - } - return make_response((message, 200)) \ No newline at end of file + # message = { + # "id": goal.goal_id, + # "title": goal.title, + # "tasks": task_list + # } + # return make_response((message, 200)) \ No newline at end of file diff --git a/app/routes/helpers.py b/app/routes/helpers.py index 9d54a57d7..ccd7b6add 100644 --- a/app/routes/helpers.py +++ b/app/routes/helpers.py @@ -1,9 +1,13 @@ from flask import jsonify, abort, make_response, request +from app.models.task import Task +from dotenv import load_dotenv import requests from datetime import datetime from app import db import os +load_dotenv() + def send_slack_message(completed_task): TOKEN = os.environ['SLACK_API_TOKEN'] AUTH_HEADERS = { diff --git a/app/routes/task_routes.py b/app/routes/task_routes.py index 5a917f01d..07599042a 100644 --- a/app/routes/task_routes.py +++ b/app/routes/task_routes.py @@ -2,6 +2,7 @@ from app.models.task import Task from app.routes.helpers import create_item, get_all_items, get_one_item, update_item, delete_item, mark_item_complete, mark_item_incomplete + tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") diff --git a/migrations/versions/a5c35fb18f9e_removed_nullables_to_try_to_correct_.py b/migrations/versions/a5c35fb18f9e_removed_nullables_to_try_to_correct_.py new file mode 100644 index 000000000..4c0ab53b2 --- /dev/null +++ b/migrations/versions/a5c35fb18f9e_removed_nullables_to_try_to_correct_.py @@ -0,0 +1,38 @@ +"""removed nullables to try to correct postman issues + +Revision ID: a5c35fb18f9e +Revises: 5c1b99b6d55b +Create Date: 2023-05-14 20:33:45.839954 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a5c35fb18f9e' +down_revision = '5c1b99b6d55b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('task', 'description', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('task', 'title', + existing_type=sa.VARCHAR(), + nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('task', 'title', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('task', 'description', + existing_type=sa.VARCHAR(), + nullable=False) + # ### end Alembic commands ### diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index 209a57a6d..aac534f81 100644 --- a/tests/test_wave_06.py +++ b/tests/test_wave_06.py @@ -53,11 +53,6 @@ def test_get_tasks_for_specific_goal_no_goal(client): assert response.status_code == 404 assert response_body == {"details": "Invalid data"} - # 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") def test_get_tasks_for_specific_goal_no_tasks(client, one_goal): From aa9bf8c86bd87977dc6feaef3fead92ae56aceea Mon Sep 17 00:00:00 2001 From: Whitney Date: Mon, 15 May 2023 14:55:53 -0700 Subject: [PATCH 19/20] final push before delpoyment, all tests passing, all response bodies correct in Postman --- app/models/goal.py | 9 +- app/routes/goal_routes.py | 20 +- app/routes/helpers.py | 6 +- app/routes/routes.py | 273 ------------------ ..._dropped_and_established_new_databses_.py} | 12 +- ...9e_removed_nullables_to_try_to_correct_.py | 38 --- tests/test_wave_01.py | 2 +- tests/test_wave_03.py | 10 +- 8 files changed, 12 insertions(+), 358 deletions(-) delete mode 100644 app/routes/routes.py rename migrations/versions/{5c1b99b6d55b_dropped_prior_databases_creating_new.py => 623d6d489109_dropped_and_established_new_databses_.py} (79%) delete mode 100644 migrations/versions/a5c35fb18f9e_removed_nullables_to_try_to_correct_.py diff --git a/app/models/goal.py b/app/models/goal.py index c44aded19..fb402c954 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -13,7 +13,7 @@ def to_dict(self, tasks=False): "id": self.goal_id, "title": self.title } - + if tasks: build_dict["tasks"] = [task.to_dict() for task in self.tasks] @@ -23,9 +23,4 @@ def to_dict(self, tasks=False): def from_dict(cls, build_dict): new_goal = cls(title=build_dict["title"]) return new_goal - - # def from_dict(cls, build_dict): - # return Goal( - # title = build_dict["title"] - # ) - + diff --git a/app/routes/goal_routes.py b/app/routes/goal_routes.py index 0bfac5c7d..1488d4cf0 100644 --- a/app/routes/goal_routes.py +++ b/app/routes/goal_routes.py @@ -38,15 +38,10 @@ def create_goal_with_tasks(goal_id): request_body = request.get_json() task_ids = request_body.get("task_ids") - # for task in goal.tasks: - # task.goal_id = None - for task_id in task_ids: task = validate_model(Task, task_id) task.goal_id = goal.goal_id - # goal.title = request.args.get("title", goal.title) - db.session.commit() message = { @@ -61,17 +56,4 @@ def create_goal_with_tasks(goal_id): def get_all_tasks_one_goal(goal_id): goal = validate_model(Goal, goal_id) - return make_response(goal.to_dict(tasks=True), 200) - - # task_list = [] - - # for task in goal.tasks: - # task = validate_model(Task, task.task_id) - # task_list.append(task.to_dict()) - - # message = { - # "id": goal.goal_id, - # "title": goal.title, - # "tasks": task_list - # } - # return make_response((message, 200)) \ No newline at end of file + return make_response(goal.to_dict(tasks=True), 200) \ No newline at end of file diff --git a/app/routes/helpers.py b/app/routes/helpers.py index ccd7b6add..3cafb789f 100644 --- a/app/routes/helpers.py +++ b/app/routes/helpers.py @@ -1,5 +1,4 @@ from flask import jsonify, abort, make_response, request -from app.models.task import Task from dotenv import load_dotenv import requests from datetime import datetime @@ -57,7 +56,6 @@ def create_item(cls): return make_response({"details": "Invalid data"}, 400) - def get_all_items(cls): sort_query = request.args.get("sort") @@ -74,9 +72,7 @@ def get_all_items(cls): if request.args.get("title"): items = cls.query.filter(cls.title== request.args.get("title")) - items_response = [] - for item in items: - items_response.append(item.to_dict()) + items_response = [item.to_dict() for item in items] return jsonify(items_response), 200 diff --git a/app/routes/routes.py b/app/routes/routes.py deleted file mode 100644 index fa4805066..000000000 --- a/app/routes/routes.py +++ /dev/null @@ -1,273 +0,0 @@ -# from flask import Blueprint, jsonify, abort, make_response, request -# import requests -# from app.models.task import Task -# from app.models.goal import Goal -# from datetime import datetime -# from app import db -# import os - -# goals_bp = Blueprint("goals", __name__, url_prefix="/goals") -# tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") - - - -# #POST /tasks -# @tasks_bp.route("", methods=["POST"]) -# def create_task(): -# try: -# request_body = request.get_json() -# new_task = Task.from_dict(request_body) -# except KeyError as err: -# return make_response({"details": "Invalid data"}, 400) - -# db.session.add(new_task) -# db.session.commit() - -# return jsonify({"task": new_task.to_dict()}), 201 - - -# #Validate Model -# def validate_model(cls, task_id): -# try: -# task_id = int(task_id) -# except: -# abort(make_response({"details": "Invalid data"}, 400)) - -# task = cls.query.get(task_id) - -# if not task: -# message = {"details": "Invalid data"} -# abort(make_response(message, 404)) - -# return task - - -# #GET /tasks -# @tasks_bp.route("", methods=["GET"]) -# def get_all_tasks(): - -# sort_query = request.args.get("sort") - -# if sort_query == "asc": -# tasks = Task.query.order_by(Task.title) - -# elif sort_query == "desc": -# tasks = Task.query.order_by(Task.title.desc()) - -# else: -# tasks = Task.query.all() - -# tasks_response = [] -# for task in tasks: -# tasks_response.append(task.to_dict()) - -# return jsonify(tasks_response), 200 - - -# #GET /tasks/ -# @tasks_bp.route("/", methods=["GET"]) -# def get_one_task(task_id): -# task = validate_model(Task, task_id) -# if task: -# return {"task": task.to_dict()}, 200 - -# else: -# return {'details': 'Invalid data'}, 404 - - -# #PUT /tasks/ -# @tasks_bp.route("", methods=["PUT"]) -# def update_task(task_id): -# try: -# task = validate_model(Task, task_id) -# except: -# return jsonify({"Message": "Invalid id"}), 404 - -# request_body = request.get_json() - -# task.title = request_body["title"] -# task.description = request_body["description"] - -# db.session.commit() - -# return jsonify({"task": task.to_dict()}), 200 - - -# #DELETE /tasks/ -# @tasks_bp.route("", methods=["DELETE"]) -# def delete_task(task_id): -# task = validate_model(Task, task_id) - -# if task is None: -# return {'details': 'Invalid data'}, 404 - -# db.session.delete(task) -# db.session.commit() - -# message = {"details": f"Task 1 \"{task.title}\" successfully deleted"} -# return make_response(message, 200) - -# #Patch //mark_complete -# @tasks_bp.route("/mark_complete", methods=["PATCH"]) -# def mark_task_complete(task_id): -# try: -# new_task = validate_model(Task, task_id) -# except: -# return jsonify({"Message": "Invalid id"}), 404 - -# new_task.completed_at = datetime.utcnow() - -# send_slack_message(new_task) - -# db.session.commit() - -# return jsonify({"task": new_task.to_dict()}), 200 - - -# @tasks_bp.route("/mark_incomplete", methods=["PATCH"]) -# def mark_task_incomplete(task_id): -# try: -# new_task = validate_model(Task, task_id) -# except: -# return jsonify({"Message": "Invalid id"}), 404 - -# new_task.completed_at = None - -# db.session.commit() - -# return jsonify({"task": new_task.to_dict()}), 200 - - - -# def send_slack_message(completed_task): -# TOKEN = os.environ['SLACK_API_TOKEN'] -# AUTH_HEADERS = { -# "Authorization": f"Bearer {TOKEN}" -# } -# CHANNEL_ID = "C0561UUDX4K" -# SLACK_URL = "https://slack.com/api/chat.postMessage" - -# try: -# message = f"Someone just completed the task {completed_task.title}" -# payload = { -# "channel": CHANNEL_ID, -# "text": message -# } - -# requests.post(SLACK_URL, data = payload, headers = AUTH_HEADERS) - -# except: -# print("There was an error making the call to Slack") - - -# #----------------------------------------------------------- - -# @goals_bp.route("", methods=["POST"]) -# def create_goal(): -# try: -# request_body = request.get_json() -# new_goal = Goal.from_dict(request_body) -# except KeyError as err: -# return make_response({"details": "Invalid data"}, 400) - -# db.session.add(new_goal) -# db.session.commit() - -# return jsonify({"goal": new_goal.to_dict()}), 201 - - -# @goals_bp.route("", methods=["GET"]) -# def get_all_goals(): - -# goals = Goal.query.all() - -# goal_response = [] -# for goal in goals: -# goal_response.append(goal.to_dict()) - -# return jsonify(goal_response), 200 - - -# @goals_bp.route("/", methods=["GET"]) -# def get_one_goal(goal_id): -# goal = validate_model(Goal, goal_id) -# if goal: -# return {"goal": goal.to_dict()}, 200 - -# else: -# return {'details': 'Invalid data'}, 404 - - -# @goals_bp.route("", methods=["PUT"]) -# def update_task(goal_id): -# try: -# goal = validate_model(Goal, goal_id) -# except: -# return jsonify({"Message": "Invalid id"}), 404 - -# request_body = request.get_json() - -# goal.title = request_body["title"] - -# db.session.commit() - -# return jsonify({"goal": goal.to_dict()}), 200 - - -# @goals_bp.route("", methods=["DELETE"]) -# def delete_goal(goal_id): -# try: -# goal = validate_model(Goal, goal_id) - -# db.session.delete(goal) -# db.session.commit() - -# message = {"details": f"Goal 1 \"{goal.title}\" successfully deleted"} -# return make_response(message, 200) - -# except: -# return {'details': 'Invalid data'}, 404 - -# #---------------------------------------------------------- - -# @goals_bp.route("//tasks", methods=['POST']) -# def create_goal_with_tasks(goal_id): - -# request_body = request.get_json() -# goal = validate_model(Goal, goal_id) -# task_list = request_body.get("task_ids") - -# tasks = [] -# for task_id in task_list: -# task = validate_model(Task, task_id) -# task.goal = goal -# tasks.append(task_id) - -# db.session.commit() - -# message = { -# "id": goal.goal_id, -# "task_ids": tasks -# } -# return make_response(message, 200) - - -# @goals_bp.route("//tasks", methods=['GET']) -# def get_all_tasks_one_goal(goal_id): -# try: -# goal = validate_model(Goal, goal_id) -# except: -# return make_response({"details": "Invalid data"}, 404) - -# task_list = [] - -# for task in goal.tasks: -# task = validate_model(Task, task.id) -# task_list.append(task.to_dict()) - -# message = { -# "id": goal.goal_id, -# "title": goal.title, -# "tasks": task_list -# } -# return make_response((message, 200)) \ No newline at end of file diff --git a/migrations/versions/5c1b99b6d55b_dropped_prior_databases_creating_new.py b/migrations/versions/623d6d489109_dropped_and_established_new_databses_.py similarity index 79% rename from migrations/versions/5c1b99b6d55b_dropped_prior_databases_creating_new.py rename to migrations/versions/623d6d489109_dropped_and_established_new_databses_.py index a03e5144a..54600358e 100644 --- a/migrations/versions/5c1b99b6d55b_dropped_prior_databases_creating_new.py +++ b/migrations/versions/623d6d489109_dropped_and_established_new_databses_.py @@ -1,8 +1,8 @@ -"""Dropped prior databases, creating new +"""Dropped and established new databses for deployment -Revision ID: 5c1b99b6d55b +Revision ID: 623d6d489109 Revises: -Create Date: 2023-05-14 18:02:04.127203 +Create Date: 2023-05-15 10:12:34.890880 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '5c1b99b6d55b' +revision = '623d6d489109' down_revision = None branch_labels = None depends_on = None @@ -25,8 +25,8 @@ def upgrade(): ) op.create_table('task', sa.Column('task_id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('title', sa.String(), nullable=False), - sa.Column('description', sa.String(), nullable=False), + sa.Column('title', sa.String(), nullable=True), + sa.Column('description', sa.String(), nullable=True), sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True), sa.Column('goal_id', sa.Integer(), nullable=True), sa.ForeignKeyConstraint(['goal_id'], ['goal.goal_id'], ), diff --git a/migrations/versions/a5c35fb18f9e_removed_nullables_to_try_to_correct_.py b/migrations/versions/a5c35fb18f9e_removed_nullables_to_try_to_correct_.py deleted file mode 100644 index 4c0ab53b2..000000000 --- a/migrations/versions/a5c35fb18f9e_removed_nullables_to_try_to_correct_.py +++ /dev/null @@ -1,38 +0,0 @@ -"""removed nullables to try to correct postman issues - -Revision ID: a5c35fb18f9e -Revises: 5c1b99b6d55b -Create Date: 2023-05-14 20:33:45.839954 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'a5c35fb18f9e' -down_revision = '5c1b99b6d55b' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('task', 'description', - existing_type=sa.VARCHAR(), - nullable=True) - op.alter_column('task', 'title', - existing_type=sa.VARCHAR(), - nullable=True) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('task', 'title', - existing_type=sa.VARCHAR(), - nullable=False) - op.alter_column('task', 'description', - existing_type=sa.VARCHAR(), - nullable=False) - # ### end Alembic commands ### diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index 7f2c23280..df69a6013 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -30,7 +30,7 @@ def test_get_tasks_one_saved_tasks(client, one_task): "is_complete": False } ] - + # @pytest.mark.skip(reason="No way to test this feature yet") def test_get_task(client, one_task): diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index 47162f496..d185258a5 100644 --- a/tests/test_wave_03.py +++ b/tests/test_wave_03.py @@ -129,11 +129,6 @@ def test_mark_complete_missing_task(client): assert response.status_code == 404 assert response_body == {'Message': 'Invalid id'} - # 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") def test_mark_incomplete_missing_task(client): @@ -145,7 +140,4 @@ def test_mark_incomplete_missing_task(client): assert response.status_code == 404 response_body == {'Message': 'Invalid id'} - # raise Exception("Complete test with assertion about response body") - # ***************************************************************** - # **Complete test with assertion about response body*************** - # ***************************************************************** + From 17fdafc17092ba18861bd44815bda4ddadc2b15a Mon Sep 17 00:00:00 2001 From: Whitney Date: Tue, 16 May 2023 14:45:07 -0700 Subject: [PATCH 20/20] Update Connection String for Render --- app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 3e097d676..c4d3a3ba9 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -16,7 +16,7 @@ def create_app(test_config=None): if test_config is None: app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get( - "SQLALCHEMY_DATABASE_URI") + "RENDER_DATABASE_URI") else: app.config["TESTING"] = True app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(