diff --git a/ada-project-docs/wave_04.md b/ada-project-docs/wave_04.md index 079ce6485..ecd391f75 100644 --- a/ada-project-docs/wave_04.md +++ b/ada-project-docs/wave_04.md @@ -102,7 +102,7 @@ Answer the following questions. These questions will help you become familiar wi - What are the _two_ _required_ arguments for this endpoint? - How does this endpoint relate to the Slackbot API key (token) we just created? -Now, visit https://api.slack.com/methods/chat.postMessage/test. +Now, visit https://api.slack.com/methods/chat.postMessatge/test. ![](assets/api_test.png) diff --git a/app/__init__.py b/app/__init__.py index 2764c4cc8..f98177d16 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,5 +30,13 @@ def create_app(test_config=None): migrate.init_app(app, db) # Register Blueprints here + # from .routes import task_list_bp + # app.register_blueprint(task_list_bp) + + from .routes import tasks_bp + app.register_blueprint(tasks_bp) + + from .routes import goals_bp + app.register_blueprint(goals_bp) return app diff --git a/app/models/goal.py b/app/models/goal.py index 8cad278f8..05603597a 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -1,6 +1,29 @@ from flask import current_app +from app.models.task import Task from app import db class Goal(db.Model): - goal_id = db.Column(db.Integer, primary_key=True) + goal_id = db.Column(db.Integer, primary_key=True, autoincrement = True) + title = db.Column(db.String) + tasks = db.relationship('Task', backref = 'goal', lazy =True) + + # def to_string(self): + # return f"{self.id}: {self.title} Description: {self.description} Completed at: {self.completed_at}" + + def to_dict(self): + return { + "id": self.goal_id, + "title": self.title + } + + def goal_with_tasks_dict(self): + return { + "id": self.goal_id, + "title": self.title, + "task_id": self.tasks.id, + "title": self.tasks.title, + "description": self.tasks.description, + "is_complete": self.tasks.completed_at + } + diff --git a/app/models/task.py b/app/models/task.py index 39c89cd16..6c10f418c 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -3,4 +3,22 @@ class Task(db.Model): - task_id = db.Column(db.Integer, primary_key=True) + id = db.Column(db.Integer, primary_key=True, autoincrement = True) + title = db.Column(db.String) + description = db.Column(db.String) + completed_at = db.Column(db.DateTime, nullable = True) + goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'), nullable = True) + #goal = db.relationship('Goal', back_populates = 'tasks', lazy = True) unnecessary because we have backref in goal model + + def to_string(self): + return f"{self.id}: {self.title} Description: {self.description} Completed at: {self.completed_at}" + + def combo_dict(self): + return { + "id": self.id, + "goal_id": self.goal_id, + "title": self.title, + "description": self.description, + "is_complete": self.completed_at is not None + } + diff --git a/app/routes.py b/app/routes.py index 8e9dfe684..b81cce62e 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,2 +1,282 @@ -from flask import Blueprint +from flask.wrappers import Request +from app import db +from app.models.task import Task +from app.models.goal import Goal +from functools import wraps +from flask import Blueprint, jsonify, make_response, request +from datetime import datetime + +#task_list_bp = Blueprint("task_list_bp", __name__) +tasks_bp = Blueprint("tasks", __name__, url_prefix = "/tasks") + +# def require_task(endpoint): +# @wraps(endpoint) +# def fn(*args, task_id, **kwargs): +# task = Task.query.get(task_id) +# if not task: +# return jsonify(None), 404 +# return endpoint(*args, task_id, **kwargs) +# return fn + +@tasks_bp.route("", methods = ["GET"]) +def manage_tasks(): + #if request.method == "GET": + sort_query = request.args.get("sort") + if sort_query == "desc": + task = Task.query.order_by(Task.title.desc()) + elif sort_query == "asc": + task = Task.query.order_by(Task.title.asc()) + else: + task = Task.query.all() + tasks_response = [] + for task in task: + tasks_response.append( + { + "id": task.id, + "title": task.title, + "description": task.description, + "is_complete": task.completed_at is not None + } + ) + return jsonify(tasks_response), 200 + +@tasks_bp.route("", methods = ["POST"]) +def post_tasks(): + request_body = request.get_json() + if "title" not in request_body: + return{ + "details": "Invalid data" + }, 400 + if "description" not in request_body: + return jsonify({"details": "Invalid data"}), 400 + if "completed_at" not in request_body: + return jsonify({"details": "Invalid data"}), 400 + new_task = Task( + title = request_body["title"], + description = request_body["description"], + completed_at = request_body["completed_at"] + ) + db.session.add(new_task) + db.session.commit() + + response_body = { + "task":{ + "id": new_task.id, + "title": new_task.title, + "description": new_task.description, + "is_complete": new_task.completed_at is not None + } + } + return jsonify(response_body), 201 + +@tasks_bp.route("/", methods = ["GET", "PUT", "DELETE"]) +def manage_task(task_id): + task = Task.query.get(task_id) + if not task: + return jsonify(None), 404 + elif request.method == "GET": + if task.goal_id: + response_body = {"task":(task.combo_dict())} + else: + response_body = { + "task":{ + "id": task.id, + "title": task.title, + "description": task.description, + "is_complete": task.completed_at is not None + } + } + return jsonify(response_body), 200 + elif request.method == "PUT": + request_body = request.get_json() + + task.title = request_body["title"] + task.description = request_body["description"] + #task.completed_at = request_body["completed_at"] + + db.session.commit() + response_body = { + "task":{ + "id": task.id, + "title": task.title, + "description": task.description, + "is_complete": task.completed_at is not None + } + } + return jsonify(response_body), 200 + elif request.method == "DELETE": + db.session.delete(task) + db.session.commit() + return jsonify( + { + "details":f'Task {task.id} "{task.title}" successfully deleted' + } + ), 200 + +@tasks_bp.route("//mark_complete", methods = ["PATCH"]) #datetime.now function +def patch_complete(task_id): + task = Task.query.get(task_id) + # request_body = request.get_json() + # if "title" not in request_body or "description" not in request_body: #or "completed_at" not in request_body: + if not task: + return jsonify(None), 404 + task.completed_at = datetime.utcnow() + db.session.commit() + response_body = { + "task":{ + "id": task.id, + "title": task.title, + "description": task.description, + "is_complete": True #in the wild maybe don't hardcode True #if statement and then you don't need an opposite function + } + } + return jsonify(response_body), 200 + +@tasks_bp.route("//mark_incomplete", methods = ["PATCH"]) #datetime.now function +def patch_incomplete(task_id): + task = Task.query.get(task_id) + #request_body = request.get_json() #test doesn't have 'request_body' + # task_id.completed_at = False + # if "title" not in request_body or "description" not in request_body: #or "completed_at" not in request_body: + # return{ + # "details": "Invalid data" + # }, 400 + #db.session.add(task_id) + if not task: + return jsonify(None), 404 + task.completed_at = None + db.session.commit() + response_body = { + "task":{ + "id": task.id, + "title": task.title, + "description": task.description, + "is_complete": False + }} + return jsonify(response_body), 200 + + + +#wave 5 +goals_bp = Blueprint("goals", __name__, url_prefix = "/goals") + +@goals_bp.route("", methods = ["GET"]) +def manage_goals(): + goal = Goal.query.all() + goals_response = [] + for goal in goal: + goals_response.append( + { + "id": goal.goal_id, + "title": goal.title, + } + ) + return jsonify(goals_response), 200 + +@goals_bp.route("", methods = ["POST"]) +def post_goals(): + request_body = request.get_json() + if "title" not in request_body: + return{ + "details": "Invalid data" + }, 400 + new_goal = Goal( + title = request_body["title"], + ) + db.session.add(new_goal) + db.session.commit() + response_body = { + "goal":{ + "id": new_goal.goal_id, + "title": new_goal.title, + } + } + return jsonify(response_body), 201 + +@goals_bp.route("/", methods = ["GET", "PUT", "DELETE"]) +def whatever_goal(goal_id): + goal = Goal.query.get(goal_id) + request_body = request.get_json() + if not goal: + return jsonify(None), 404 + if request.method == "GET": + response_body = { + "goal":{ + "id": goal.goal_id, + "title": goal.title, + } + } + return jsonify(response_body), 200 + elif request.method == "PUT": + request_body = request.get_json() + goal.title = request_body["title"] + db.session.commit() + response_body = { + "goal":{ + "id": goal.goal_id, + "title": goal.title, + } + } + return jsonify(response_body), 201 + elif request.method == "DELETE": + db.session.delete(goal) + db.session.commit() + return jsonify( + { + "details":f'Goal {goal.goal_id} "{goal.title}" successfully deleted' + } + ), 200 + +@goals_bp.route("goals//mark_complete", methods = ["PATCH"]) +def goal_patch_complete(goal, goal_id): + request_body = request.get_json() + goal.completed_at = True + if "title" not in request_body: + return{ + "details": "Invalid data" + }, 400 + db.session.add(goal) + db.session.commit() + response_body = { + "goal":{ + "id": goal.goal_id, + "title": goal.title, + }} + return jsonify(response_body), 201 + +#wave 6 +@goals_bp.route("//tasks", methods = ["GET", "POST"]) +def task_list_to_goal(goal_id): + goal = Goal.query.get(goal_id) + request_body = request.get_json() + if goal == None: + return jsonify(None), 404 + elif request.method == "GET": + return jsonify(goal.goal_with_tasks_dict()), 200 + elif request.method == "POST": + request_body = request.get_json() + task_ids = request_body["task_ids"] + for id in task_ids: + task = Task.query.get(id) + task.goal_id = goal_id + db.session.commit() + new_tasks = [] + for task in goal.tasks: + new_tasks.append(task.id) + response_body = { + "id": int(goal_id), + "task_ids": new_tasks + } + return jsonify(response_body), 200 + + + + + + + + + + + diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index cc6459a12..e8cede348 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -122,7 +122,7 @@ def test_update_task_not_found(client): assert response_body == None -def test_delete_task(client, one_task): +def test_delete_task(client, one_task): #anything that looks like a param in a test is actually the name of a fixture that gets run before the test runs; see line 31 in conftest.py # Act response = client.delete("/tasks/1") response_body = response.get_json() diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index 6ba60c6fa..b89643321 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -1,4 +1,5 @@ import pytest +from app.models.goal import Goal def test_get_goals_no_saved_goals(client): # Act @@ -41,19 +42,22 @@ def test_get_goal(client, one_goal): } } -@pytest.mark.skip(reason="test to be completed by student") -def test_get_goal_not_found(client): - pass +#@pytest.mark.skip(reason="test to be completed by student") + #pass # Act - response = client.get("/goals/1") - response_body = response.get_json() + #client = Goal.query.get("/goals/1") + # response = client.get("/goals/1") + # response_body = response.get_json() # Assert - # ---- Complete Test ---- - # assertion 1 goes here - # assertion 2 goes here - # ---- Complete Test ---- - + # assert response.status_code == 404 + # assert "goal" in response_body + # assert response_body == { + # "goal": { + # "id": 1, + # "title": "Walk outdoors daily", + # } + # } def test_create_goal(client): # Act response = client.post("/goals", json={ @@ -71,30 +75,40 @@ 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): - pass + #pass # Act - # ---- Complete Act Here ---- + response = client.put("/goals/1", json={ + "title": "Updated Goal Title", + }) + response_body = response.get_json() # Assert - # ---- Complete Assertions Here ---- - # assertion 1 goes here - # assertion 2 goes here - # assertion 3 goes here - # ---- Complete Assertions Here ---- + assert response.status_code == 201 + assert "goal" in response_body + assert response_body == { + "goal": { + "id": 1, + "title": "Updated Goal Title", + } + } + # one_goal = Goal.query.get(1) + # assert one_goal.title == "Updated Goal Title" + -@pytest.mark.skip(reason="test to be completed by student") +#@pytest.mark.skip(reason="test to be completed by student") def test_update_goal_not_found(client): - pass + #pass # Act - # ---- Complete Act Here ---- + response = client.put("/goals/1", json={ + "title": "Updated Goal Title", + }) + response_body = response.get_json() # Assert - # ---- Complete Assertions Here ---- - # assertion 1 goes here - # assertion 2 goes here - # ---- Complete Assertions Here ---- + assert response.status_code == 404 + assert response_body == None def test_delete_goal(client, one_goal): @@ -113,18 +127,19 @@ def test_delete_goal(client, one_goal): response = client.get("/goals/1") assert response.status_code == 404 -@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): - pass + #pass - # Act - # ---- Complete Act Here ---- + # Act + response = client.delete("/goals/1") + response_body = response.get_json() # Assert - # ---- Complete Assertions Here ---- - # assertion 1 goes here - # assertion 2 goes here - # ---- Complete Assertions Here ---- + assert response.status_code == 404 + assert response_body == None + assert Goal.query.all() == [] + def test_create_goal_missing_title(client):