-
Notifications
You must be signed in to change notification settings - Fork 124
Kunzite - Diana Salazar #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
27c8bad
fe59e25
4811966
4b05937
ba503b2
44e14d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,107 @@ | ||||||||||||||
| from os import abort | ||||||||||||||
| from app import db | ||||||||||||||
| from app.models.goal import Goal | ||||||||||||||
| from app.models.task import Task | ||||||||||||||
| from flask import Blueprint, jsonify, abort, make_response, request | ||||||||||||||
| from datetime import datetime | ||||||||||||||
| import requests | ||||||||||||||
| import os | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| goals_bp = Blueprint("goal", __name__, url_prefix="/goals") | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @goals_bp.route("", methods=["POST"]) | ||||||||||||||
| def create_goal(): | ||||||||||||||
| request_body = request.get_json() | ||||||||||||||
| if "title" not in request_body: | ||||||||||||||
| return make_response(jsonify({"details": "Invalid data"}), 400) | ||||||||||||||
|
|
||||||||||||||
| new_goal = Goal.from_dict(request_body) | ||||||||||||||
| db.session.add(new_goal) | ||||||||||||||
| db.session.commit() | ||||||||||||||
|
|
||||||||||||||
| return make_response(jsonify(new_goal.to_dict()), 201) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @goals_bp.route("", methods=["GET"]) | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||||||||||||||
| def get_goals(): | ||||||||||||||
| goals = Goal.query.all() | ||||||||||||||
| goal_list = [goal.to_dict()["goal"]for goal in goals] | ||||||||||||||
| return make_response(jsonify(goal_list), 200) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @goals_bp.route("/<goal_id>", methods=["GET"]) | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||||||||||||||
| def read_one_goal(goal_id): | ||||||||||||||
| goal = Goal.validate_goal(goal_id) | ||||||||||||||
| return make_response(jsonify(goal.to_dict()), 200) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @goals_bp.route("/<goal_id>", methods=["PUT"]) | ||||||||||||||
| def update_goal(goal_id): | ||||||||||||||
| goal = Goal.validate_goal(goal_id) | ||||||||||||||
| request_body = request.get_json() | ||||||||||||||
| goal.title = request_body["title"] | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There might be only one attribute, but for any model we could turn this into an instance method for updating! |
||||||||||||||
| db.session.commit() | ||||||||||||||
| return make_response(jsonify(goal.to_dict()), 200) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @goals_bp.route("/<goal_id>", methods=["DELETE"]) | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||||||||||||||
| def delete_goal(goal_id): | ||||||||||||||
| goal = Goal.validate_goal(goal_id) | ||||||||||||||
| db.session.delete(goal) | ||||||||||||||
| db.session.commit() | ||||||||||||||
| return make_response(jsonify({"details": f'Goal {goal_id} "{goal.title}" successfully deleted'})) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||||||||||||||
| def add_tasks_to_goal(goal_id): | ||||||||||||||
| goal = Goal.validate_goal(goal_id) | ||||||||||||||
|
|
||||||||||||||
| data = request.json | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on documentation, sounds like we should stick with data = request.get_json() |
||||||||||||||
| task_ids = data.get("task_ids", []) | ||||||||||||||
| tasks = Task.query.filter(Task.task_id.in_(task_ids)).all() | ||||||||||||||
|
|
||||||||||||||
| goal.tasks.extend(tasks) | ||||||||||||||
|
|
||||||||||||||
| db.session.commit() | ||||||||||||||
|
|
||||||||||||||
| return { | ||||||||||||||
| "id": goal.goal_id, | ||||||||||||||
| "task_ids": task_ids | ||||||||||||||
| }, 200 | ||||||||||||||
|
Comment on lines
+69
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep consistent with your return statements. Since you've previously used |
||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||||||||||||||
| def get_tasks_for_goal(goal_id): | ||||||||||||||
| goal = Goal.validate_goal(goal_id) | ||||||||||||||
| tasks = Task.query.filter_by(goal_id = goal.goal_id).all() | ||||||||||||||
| task_list = [] | ||||||||||||||
|
|
||||||||||||||
| for task in tasks: | ||||||||||||||
| task_dict = { | ||||||||||||||
| "id": task.task_id, | ||||||||||||||
| "goal_id": goal.goal_id, | ||||||||||||||
| "title": task.title, | ||||||||||||||
| "description": task.description, | ||||||||||||||
| "is_complete": task.completed_at is not None | ||||||||||||||
| } | ||||||||||||||
| task_list.append(task_dict) | ||||||||||||||
|
Comment on lines
+80
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be a good candidate for a separate helper function! |
||||||||||||||
|
|
||||||||||||||
| response_body = { | ||||||||||||||
| "id": goal.goal_id, | ||||||||||||||
| "title": goal.title, | ||||||||||||||
| "tasks": task_list | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+92
to
+96
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's turn this into an instance method in the |
||||||||||||||
|
|
||||||||||||||
| return make_response(jsonify(response_body), 200) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| # @goals_bp.route("/tasks/<task_id>", methods=["GET"]) | ||||||||||||||
| # def get_task(task_id): | ||||||||||||||
| # task = Task.validate_task(task_id) | ||||||||||||||
| # task_dict = task.to_dict_with_goal_id() | ||||||||||||||
|
|
||||||||||||||
| # return jsonify(task_dict), 200 | ||||||||||||||
|
Comment on lines
+101
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's get rid of this code since we aren't using it.
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,40 @@ | ||
| from app import db | ||
| from flask import make_response, jsonify, abort | ||
| from datetime import datetime | ||
|
|
||
|
|
||
| class Goal(db.Model): | ||
| goal_id = db.Column(db.Integer, primary_key=True) | ||
| title = db.Column(db.String, nullable=False) | ||
|
|
||
| tasks = db.relationship("Task", backref="goal", lazy=True) | ||
|
|
||
|
|
||
| def to_dict(self): | ||
| return {"goal": { | ||
| "goal_id": self.goal_id, | ||
| "title": self.title, | ||
| } | ||
| } | ||
|
|
||
|
|
||
| @classmethod | ||
| def from_dict(cls, data): | ||
| title = data.get("title", "") | ||
| return Goal(title=title) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if the class name is changed on line 6? We'd have to remember to change it here, too! Or! we can use the return cls(title=title) |
||
|
|
||
|
|
||
| @classmethod | ||
| def validate_goal(cls, goal_id): | ||
| try: | ||
| goal_id = int(goal_id) | ||
| except: | ||
| abort(make_response(jsonify({"message": f"{cls.__name__} {goal_id} invalid"}), 400)) | ||
|
|
||
| goal = cls.query.get(goal_id) | ||
|
|
||
| if not goal: | ||
| abort(make_response(jsonify({"message": f"{cls.__name__} {goal_id} not found"}), 404)) | ||
|
|
||
| return goal | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,5 +1,55 @@ | ||||||
| from app import db | ||||||
|
|
||||||
| from flask import make_response, jsonify, abort | ||||||
| from datetime import datetime | ||||||
|
|
||||||
| class Task(db.Model): | ||||||
| task_id = db.Column(db.Integer, primary_key=True) | ||||||
| title = db.Column(db.String, nullable=False) | ||||||
| description = db.Column(db.String, nullable=False) | ||||||
| completed_at = db.Column(db.DateTime, nullable = True) | ||||||
| goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True) | ||||||
|
|
||||||
| def to_dict(self): | ||||||
| return {"task": { | ||||||
| "task_id": self.task_id, | ||||||
| "title": self.title, | ||||||
| "description": self.description, | ||||||
| "is_complete": self.completed_at is not None | ||||||
| }} | ||||||
|
|
||||||
|
|
||||||
| def to_dict_with_goal_id(self): | ||||||
| task_dict = { | ||||||
| "task_id": self.task_id, | ||||||
| "title": self.title, | ||||||
| "description": self.description, | ||||||
| "is_complete": self.completed_at is not None, | ||||||
| "goal_id": self.goal_id | ||||||
| } | ||||||
| return {"task": task_dict} | ||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
| @classmethod | ||||||
| def from_dict(cls, data): | ||||||
| title = data.get("title", "") | ||||||
| description = data.get("description", "") | ||||||
| completed_at = data.get("is_complete", None) | ||||||
| return Task(title=title, description=description, completed_at=completed_at) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
|
|
||||||
| @classmethod | ||||||
| def validate_task(cls, task_id): | ||||||
| try: | ||||||
| task_id = int(task_id) | ||||||
| except: | ||||||
| abort(make_response(jsonify({"message": f"{cls.__name__} {task_id} invalid"}), 400)) | ||||||
|
|
||||||
| task = cls.query.get(task_id) | ||||||
|
|
||||||
| if not task: | ||||||
| abort(make_response(jsonify({"message": f"{cls.__name__} {task_id} not found"}), 404)) | ||||||
|
|
||||||
| return task | ||||||
|
|
||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| from os import abort | ||
| from app import db | ||
| from app.models.task import Task | ||
| from flask import Blueprint, jsonify, abort, make_response, request | ||
| from datetime import datetime | ||
| import requests | ||
| import os | ||
|
|
||
| tasks_bp = Blueprint("task", __name__, url_prefix="/tasks") | ||
|
|
||
|
|
||
| @tasks_bp.route("", methods=["POST"]) | ||
| def create_task(): | ||
| request_body = request.get_json() | ||
| if "description" not in request_body: | ||
| return make_response(jsonify({"details": "Invalid data"}), 400) | ||
| if "title" not in request_body: | ||
| return make_response(jsonify({"details": "Invalid data"}), 400) | ||
|
Comment on lines
+15
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the return statements are identical, let's combine these together: if "description" not in request_body or "title" not in request_body:
return make_response(jsonify({"details": "Invalid data"}), 400) |
||
| new_task = Task.from_dict(request_body) | ||
| db.session.add(new_task) | ||
| db.session.commit() | ||
| return make_response(jsonify(new_task.to_dict()), 201) | ||
|
|
||
|
|
||
| @tasks_bp.route("", methods=["GET"]) | ||
| def get_tasks(): | ||
| sort_order = request.args.get("sort", "asc") | ||
| sort_key = lambda task: task.title | ||
| tasks = Task.query.all() | ||
| if len(tasks) > 1: | ||
| tasks = sorted(tasks, key=sort_key) | ||
| if sort_order == "desc": | ||
| tasks = reversed(tasks) | ||
|
Comment on lines
+27
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be a good candidate for a separate helper function! |
||
| task_list = [task.to_dict()["task"]for task in tasks] | ||
| return make_response(jsonify(task_list), 200) | ||
|
|
||
|
|
||
| @tasks_bp.route("/<task_id>", methods=["GET"]) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
| def read_one_task(task_id): | ||
| task = Task.validate_task(task_id) | ||
| return make_response(jsonify(task.to_dict()), 200) | ||
|
|
||
|
|
||
| @tasks_bp.route("/<task_id>", methods=["PUT"]) | ||
| def update_task(task_id): | ||
| task = Task.validate_task(task_id) | ||
| request_body = request.get_json() | ||
| task.title = request_body["title"] | ||
| task.description = request_body["description"] | ||
| task.completed_at = request_body.get("completed_at", None) | ||
|
Comment on lines
+48
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be a good candidate for an instance method in the |
||
| db.session.commit() | ||
| return make_response(jsonify(task.to_dict()), 200) | ||
|
|
||
|
|
||
| @tasks_bp.route("/<task_id>", methods=["DELETE"]) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
| def delete_task(task_id): | ||
| task = Task.validate_task(task_id) | ||
| db.session.delete(task) | ||
| db.session.commit() | ||
| return make_response(jsonify({"details": f'Task {task_id} "{task.title}" successfully deleted'})) | ||
|
|
||
|
|
||
| @tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||
| def mark_complete(task_id): | ||
| task = Task.validate_task(task_id) | ||
|
|
||
| if task.completed_at is None: | ||
| task.completed_at = datetime.now() | ||
| db.session.commit() | ||
|
|
||
| slack_token = os.environ.get("SLACK_TOKEN") | ||
| if slack_token: | ||
| channel = "#task-notifications" | ||
| message = f'Someone just completed the task "{task.title}."' | ||
| url = "https://slack.com/api/chat.postMessage" | ||
| headers = { | ||
| "Authorization": f"Bearer {slack_token}", | ||
| "Content-Type": "application/json" | ||
| } | ||
| payload = { | ||
| "channel":channel, | ||
| "text": message | ||
| } | ||
| custom_headers = request.headers.get("Custom-Headers") | ||
| if custom_headers is not None: | ||
| headers.update(custom_headers) | ||
| try: | ||
| response = requests.post(url, json=payload) | ||
| response.raise_for_status() | ||
| except requests.exceptions.RequestException as e: | ||
| abort(make_response(jsonify({"message": "Fail to send Slack message"}), 500)) | ||
|
|
||
| response = requests.post(url, json=payload, headers=headers) | ||
|
Comment on lines
+71
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's turn this into a separate helper function! |
||
| print(response.status_code) # Check the status code | ||
| print(response.json()) # Print the response content | ||
| return make_response(jsonify(task.to_dict()), 200) | ||
|
|
||
|
|
||
| @tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
| def mark_incomplete(task_id): | ||
| task = Task.validate_task(task_id) | ||
| if not task: | ||
| abort(make_response(jsonify({"message": f"Task {task_id} not found"}), 404)) | ||
| if task.completed_at is not None: | ||
| task.completed_at = None | ||
| db.session.commit() | ||
| return make_response(jsonify(task.to_dict()), 200) | ||
|
|
||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Generic single-database configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I like this organization here! Good guard clause, organized, helper functions. Looks great!