-
Notifications
You must be signed in to change notification settings - Fork 71
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
Scissors - Araceli #68
base: master
Are you sure you want to change the base?
Changes from all commits
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 @@ | ||
web: gunicorn "app:create_app()" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,14 @@ | ||
from flask import current_app | ||
from app import db | ||
|
||
# from app.models.task import Task | ||
|
||
class Goal(db.Model): | ||
goal_id = db.Column(db.Integer, primary_key=True) | ||
title = db.Column(db.String) | ||
|
||
def to_json(self): | ||
return { | ||
"id": self.goal_id, | ||
"title": self.title, | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,40 @@ | ||
from flask import current_app | ||
from flask import request, current_app | ||
from app import db | ||
# from app.models.goal import Goal | ||
|
||
|
||
class Task(db.Model): | ||
task_id = db.Column(db.Integer, primary_key=True) | ||
task_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", backref=db.backref("tasks"), lazy=True) | ||
|
||
def to_dict(self): | ||
return { | ||
"id": self.task_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": bool(self.completed_at) | ||
} | ||
|
||
def to_dict_goal(self): | ||
return { | ||
"id": self.task_id, | ||
"goal_id": self.goal_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": bool(self.completed_at) | ||
} | ||
Comment on lines
+14
to
+29
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. Same issue as Goal, but these are great helper functions! |
||
|
||
|
||
# def completed_task(self): | ||
# if self.completed_at == None: | ||
# completed = False | ||
# else: | ||
# completed = True | ||
|
||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,263 @@ | ||
from flask import Blueprint | ||
from flask.wrappers import Response | ||
from app import db | ||
from app.models.task import Task, to_dict, to_dict_goal | ||
from app.models.goal import Goal, to_json | ||
from flask import Blueprint, request, make_response, jsonify | ||
from datetime import datetime | ||
from dotenv import load_dotenv | ||
import os | ||
import requests | ||
|
||
tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") | ||
goals_bp = Blueprint("goals", __name__, url_prefix="/goals") | ||
load_dotenv() | ||
|
||
# tasks | ||
|
||
@tasks_bp.route("", methods=["POST"], strict_slashes=False) | ||
def create_task(): | ||
|
||
request_body = request.get_json() | ||
|
||
response = {"details": "Invalid data"} | ||
|
||
if "title" not in request_body.keys() or "description" not in request_body.keys() or "completed_at" not in request_body.keys(): | ||
|
||
return jsonify(response), 400 | ||
|
||
else: | ||
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. It's not necessary to include this |
||
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() | ||
valid_task = {"task": to_dict(new_task)} | ||
|
||
return jsonify(valid_task), 201 | ||
|
||
|
||
@tasks_bp.route("", methods=["GET"], strict_slashes=False) | ||
def get_tasks(): | ||
|
||
tasks_response = [] | ||
|
||
sort_query = request.args.get("sort") | ||
|
||
if sort_query == "asc": | ||
tasks = Task.query.order_by(Task.title.asc()) | ||
|
||
elif sort_query == "desc": | ||
tasks = Task.query.order_by(Task.title.desc()) | ||
|
||
else: | ||
tasks = Task.query.all() | ||
Comment on lines
+44
to
+51
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. Nice! |
||
|
||
for task in tasks: | ||
tasks_response.append(to_dict(task)) | ||
|
||
return jsonify(tasks_response), 200 | ||
|
||
|
||
@tasks_bp.route("/<task_id>", methods=["GET", "PUT", "DELETE"], strict_slashes=False) | ||
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. In the "" route above (lines 17 & 37), the methods are split into separate functions, while here they're combined into the same method. Both ways are fine, but for readability, I recommend following the same patterns throughout your code. |
||
def handle_task(task_id): | ||
|
||
task = Task.query.get(task_id) | ||
|
||
if request.method == "GET": | ||
if task is None: | ||
return make_response(f"404 Not Found", 404) | ||
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. Having an error message is great, but I would suggest having a more meaningful error message, something like 'Task with ID X not found'. Also this test is something you are doing for all of the methods - it could be moved up before the checks for the type of method so it only needs to be done once. |
||
|
||
else: | ||
one_task = to_dict(task) | ||
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. Here is where the last test fails - if the task has a valid goal_id, it should include the goal_id in the json output. One way to handle this is:
|
||
|
||
return {"task": one_task} | ||
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. For readability I recommend using the same format to create your responses throughout your project. Lines 56, 66 and 71 all use a different format. Flask handles all of these formats gracefully, but having multiple formats can make it seem that different things are happening when in fact they are all more or less the same. Also, I recommend explicitly setting a status code everywhere so that you can accurately predict what your API will do in each situation. |
||
|
||
|
||
elif request.method == "PUT": | ||
if task: | ||
form_data = request.get_json() | ||
task.title = form_data["title"] | ||
task.description = form_data["description"] | ||
task.is_complete = form_data["completed_at"] | ||
db.session.commit() | ||
|
||
updated_task = { | ||
"id": task.task_id, | ||
"title": task.title, | ||
"description": task.description, | ||
"is_complete": bool(task.completed_at) | ||
} | ||
Comment on lines
+82
to
+87
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 great place to use use the to_dict helper function. |
||
else: | ||
return make_response(f"", 404) | ||
|
||
return {'task': updated_task} | ||
|
||
elif request.method == "DELETE": | ||
if task: | ||
db.session.delete(task) | ||
db.session.commit() | ||
|
||
response = {"details": f"Task {task.task_id} \"{task.title}\" successfully deleted"} | ||
|
||
return jsonify(response), 200 | ||
|
||
else: | ||
return make_response(f"", 404) | ||
|
||
|
||
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"], strict_slashes=False) | ||
def mark_complete(task_id): | ||
|
||
task = Task.query.get(task_id) | ||
|
||
if task is None: | ||
return jsonify(None), 404 | ||
|
||
task.completed_at = datetime.utcnow() | ||
db.session.commit() | ||
|
||
slack_bot_notification("Did this work") | ||
|
||
return jsonify({"task": to_dict(task)}), 200 | ||
|
||
def slack_bot_notification(message): | ||
path = "https://slack.com/api/chat.postMessage" | ||
SLACK_KEY = os.environ.get("SLACK_TOKEN") | ||
headers = {"Authorization": f"Bearer {SLACK_KEY}"} | ||
query_params = {"channel": "task-notifications", "text": message} | ||
requests.post(path, params=query_params, headers=headers) | ||
|
||
|
||
@tasks_bp.route("<task_id>/mark_incomplete", methods=["PATCH"], strict_slashes=False) | ||
def mark_incomplete(task_id): | ||
|
||
task = Task.query.get(task_id) | ||
|
||
if task is None: | ||
return jsonify(None), 404 | ||
|
||
task.completed_at = None | ||
db.session.commit() | ||
|
||
return jsonify({"task": to_dict(task)}), 200 | ||
|
||
|
||
# goals | ||
|
||
@goals_bp.route("", methods=["POST"], strict_slashes=False) | ||
def create_goal(): | ||
|
||
request_body = request.get_json() | ||
|
||
response = {"details": "Invalid data"} | ||
|
||
if "title" not in request_body.keys(): | ||
|
||
return jsonify(response), 400 | ||
|
||
else: | ||
new_goal = Goal(title = request_body["title"]) | ||
db.session.add(new_goal) | ||
db.session.commit() | ||
valid_goal = {"goal": to_json(new_goal)} | ||
|
||
return jsonify(valid_goal), 201 | ||
|
||
@goals_bp.route("", methods=["GET"], strict_slashes=False) | ||
def get_goals(): | ||
|
||
goals = Goal.query.all() | ||
goals_response = [] | ||
|
||
if goals != None: | ||
|
||
for goal in goals: | ||
goals_response.append(to_json(goal)) | ||
|
||
return jsonify(goals_response), 200 | ||
|
||
return jsonify(goals_response), 200 | ||
|
||
|
||
@goals_bp.route("/<goal_id>", methods=["GET", "PUT", "DELETE"], strict_slashes=False) | ||
def handle_goal(goal_id): | ||
|
||
goal = Goal.query.get(goal_id) | ||
|
||
if request.method == "GET": | ||
if goal is None: | ||
return make_response("", 404) | ||
|
||
else: | ||
valid_goal = {"goal": to_json(goal)} | ||
|
||
return jsonify(valid_goal), 200 | ||
|
||
elif request.method == "PUT": | ||
if goal: | ||
form_data = request.get_json() | ||
goal.title = form_data["title"] | ||
db.session.commit() | ||
|
||
updated_goal = { | ||
"id": goal.goal_id, | ||
"title": goal.title | ||
} | ||
|
||
else: | ||
return make_response("", 404) | ||
|
||
return {'goal': updated_goal} | ||
|
||
elif request.method == "DELETE": | ||
if goal: | ||
db.session.delete(goal) | ||
db.session.commit() | ||
|
||
response = {"details": f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"} | ||
|
||
return jsonify(response), 200 | ||
|
||
else: | ||
return make_response(f"", 404) | ||
|
||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["POST", "GET"], strict_slashes=False) | ||
def goal_task_relationship(goal_id): | ||
|
||
goal = Goal.query.get(goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
if request.method == "POST": | ||
|
||
task_ids = request_body["task_ids"] | ||
|
||
for task_id in task_ids: | ||
task = Task.query.get(task_id) | ||
task.goal_id = goal_id # or goal.tasks.append(task) | ||
|
||
db.session.commit() | ||
|
||
return {"id": int(goal_id), "task_ids": task_ids}, 200 | ||
|
||
elif request.method == "GET": | ||
|
||
# tasks_list = [] | ||
|
||
if goal: | ||
tasks = goal.tasks | ||
|
||
# for task in tasks: | ||
# tasks_list.append(to_dict_goal(task)) | ||
|
||
task_list = [to_dict_goal(task) for task in tasks] | ||
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. Nice list comprehension! |
||
|
||
return { | ||
"id": goal.goal_id, | ||
"title": goal.title, | ||
"tasks": task_list | ||
}, 200 | ||
|
||
else: | ||
return make_response("", 404) | ||
|
||
|
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.
This is a great helper function, but I suspect it wasn't working as you expected because the tab level is outside of the class.