-
Notifications
You must be signed in to change notification settings - Fork 111
Philomena Kelly - Sharks #103
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: 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,5 +1,35 @@ | ||
from app import db | ||
from datetime import datetime | ||
|
||
|
||
class Goal(db.Model): | ||
goal_id = db.Column(db.Integer, primary_key=True) | ||
title = db.Column(db.String) | ||
tasks = db.relationship("Task", back_populates="goal") | ||
|
||
|
||
def to_json(self, category=None): | ||
|
||
goal_dict = { | ||
"id": self.goal_id, | ||
"title": self.title, | ||
} | ||
|
||
if category == "tasks": | ||
goal_dict["tasks"] = [ | ||
task.to_json() for task in self.tasks | ||
] | ||
|
||
return goal_dict | ||
|
||
|
||
def update(self,req_body): | ||
self.title = req_body["title"] | ||
|
||
|
||
@classmethod | ||
def create(cls,req_body): | ||
new_goal = cls( | ||
title=req_body['title'], | ||
) | ||
return new_goal |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,5 +1,46 @@ | ||||||||||||
from app import db | ||||||||||||
from datetime import datetime | ||||||||||||
|
||||||||||||
|
||||||||||||
class Task(db.Model): | ||||||||||||
task_id = db.Column(db.Integer, primary_key=True) | ||||||||||||
title = db.Column(db.String) | ||||||||||||
description = db.Column(db.String) | ||||||||||||
completed_at = db.Column(db.DateTime, nullable=True) | ||||||||||||
is_complete = False | ||||||||||||
goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id')) | ||||||||||||
goal = db.relationship("Goal", back_populates="tasks") | ||||||||||||
|
||||||||||||
|
||||||||||||
def to_json(self): | ||||||||||||
task_dict = { | ||||||||||||
"id": self.task_id, | ||||||||||||
"title": self.title, | ||||||||||||
"description": self.description, | ||||||||||||
"is_complete" : self.is_complete | ||||||||||||
} | ||||||||||||
|
||||||||||||
if self.goal_id: | ||||||||||||
task_dict["goal_id"] = self.goal_id | ||||||||||||
|
||||||||||||
return task_dict | ||||||||||||
|
||||||||||||
def written_out(cls): | ||||||||||||
return "task" | ||||||||||||
Comment on lines
+28
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. I think this would make for a good
Suggested change
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. also is this used anywhere? I couldn't find any files where this method was called? If that's the case, let's get rid of it |
||||||||||||
|
||||||||||||
def update(self,req_body): | ||||||||||||
self.title = req_body["title"] | ||||||||||||
self.description = req_body["description"] | ||||||||||||
|
||||||||||||
if req_body.get("completed_at"): | ||||||||||||
self.is_complete = True | ||||||||||||
self.completed_at = datetime.now() | ||||||||||||
|
||||||||||||
|
||||||||||||
@classmethod | ||||||||||||
def create(cls,req_body): | ||||||||||||
new_task = cls( | ||||||||||||
title=req_body['title'], | ||||||||||||
description=req_body['description'], | ||||||||||||
) | ||||||||||||
return new_task |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1 +1,255 @@ | ||||||
from flask import Blueprint | ||||||
from os import abort | ||||||
from app import db | ||||||
from app.models.task import Task | ||||||
from flask import Blueprint, jsonify, abort, make_response, request | ||||||
from sqlalchemy.sql import text | ||||||
from datetime import datetime | ||||||
import requests | ||||||
import os | ||||||
from dotenv import load_dotenv | ||||||
from app.models.goal import Goal | ||||||
|
||||||
tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") | ||||||
goals_bp = Blueprint("goals_bp", __name__, url_prefix="/goals") | ||||||
Comment on lines
+12
to
+13
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. often in industry we will separate out routes based on models into their own file to help balance the workload on files, as well as making sure teammates don't step on each other's toes when coding |
||||||
|
||||||
|
||||||
@goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||||||
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 create_task(goal_id): | ||||||
|
||||||
goal = validate_object("goal", goal_id) | ||||||
|
||||||
request_body = request.get_json() | ||||||
|
||||||
for task in request_body["task_ids"]: | ||||||
task = validate_object("task", task) | ||||||
task.goal_id = goal_id | ||||||
Comment on lines
+23
to
+25
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 make a good instance method |
||||||
|
||||||
db.session.commit() | ||||||
|
||||||
tasks = [] | ||||||
|
||||||
for task in goal.tasks: | ||||||
tasks.append(task.task_id) | ||||||
Comment on lines
+29
to
+32
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 here, we can make this into an instance method and take it out of the route entirely |
||||||
|
||||||
response = { | ||||||
"id" : goal.goal_id, | ||||||
"task_ids" : tasks | ||||||
} | ||||||
|
||||||
return make_response(jsonify(response), 200) | ||||||
|
||||||
|
||||||
|
||||||
@goals_bp.route("/<goal_id>/tasks", 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_tasks_by_goals(goal_id): | ||||||
|
||||||
goal = validate_object("goal", goal_id) | ||||||
|
||||||
response = goal.to_json(category="tasks") | ||||||
|
||||||
return make_response(jsonify(response), 200) | ||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
@goals_bp.route("", methods=["POST"]) | ||||||
def create_one_goal(): | ||||||
|
||||||
request_body = request.get_json() | ||||||
|
||||||
try: | ||||||
new_goal = Goal.create(request_body) | ||||||
except KeyError: | ||||||
return make_response(jsonify({"details" : "Invalid data"}), 400) | ||||||
|
||||||
db.session.add(new_goal) | ||||||
db.session.commit() | ||||||
|
||||||
response = { "goal": new_goal.to_json()} | ||||||
|
||||||
return make_response(response, 201) | ||||||
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. You are staying super consistent with your return statements with |
||||||
|
||||||
|
||||||
|
||||||
@goals_bp.route("", methods=["GET"]) | ||||||
def read_all_goals(): | ||||||
title_query = request.args.get("sort") | ||||||
|
||||||
if title_query: | ||||||
goals = Goal.query.order_by(text(f'title {title_query}')) | ||||||
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. out of curiosity, what does the |
||||||
else: | ||||||
goals = Goal.query.all() | ||||||
|
||||||
goals_response = [] | ||||||
for goal in goals: | ||||||
goals_response.append( | ||||||
goal.to_json() | ||||||
) | ||||||
return make_response(jsonify(goals_response), 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 get_one_goal(goal_id): | ||||||
goal = validate_object("goal", goal_id) | ||||||
|
||||||
response = { "goal": goal.to_json()} | ||||||
|
||||||
return make_response(response, 200) | ||||||
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
|
||||||
|
||||||
|
||||||
@goals_bp.route("/<goal_id>", methods=["PUT"]) | ||||||
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 update_one_goal(goal_id): | ||||||
|
||||||
goal = validate_object("goal", goal_id) | ||||||
|
||||||
request_body = request.get_json() | ||||||
|
||||||
goal.update(request_body) | ||||||
|
||||||
db.session.commit() | ||||||
|
||||||
response = { "goal": goal.to_json()} | ||||||
return make_response(response, 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_one_goal(goal_id): | ||||||
|
||||||
goal = validate_object("goal", goal_id) | ||||||
|
||||||
db.session.delete(goal) | ||||||
db.session.commit() | ||||||
|
||||||
return make_response(jsonify({"details" : f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"})) | ||||||
|
||||||
|
||||||
@tasks_bp.route("", methods=["POST"]) | ||||||
def create_task(): | ||||||
|
||||||
request_body = request.get_json() | ||||||
try: | ||||||
new_task = Task.create(request_body) | ||||||
except KeyError: | ||||||
return make_response(jsonify({"details" : "Invalid data"}), 400) | ||||||
|
||||||
if request_body.get("completed_at"): | ||||||
new_task.is_complete = True | ||||||
new_task.completed_at = datetime.now() | ||||||
Comment on lines
+135
to
+137
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's a good place to turn this into an class method when creating a new task |
||||||
|
||||||
db.session.add(new_task) | ||||||
db.session.commit() | ||||||
|
||||||
response = { "task": new_task.to_json()} | ||||||
|
||||||
return make_response(response, 201) | ||||||
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
|
||||||
|
||||||
|
||||||
|
||||||
@tasks_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 read_all_tasks(): | ||||||
title_query = request.args.get("sort") | ||||||
|
||||||
if title_query: | ||||||
tasks = Task.query.order_by(text(f'title {title_query}')) | ||||||
else: | ||||||
tasks = Task.query.all() | ||||||
|
||||||
tasks_response = [] | ||||||
for task in tasks: | ||||||
tasks_response.append( | ||||||
task.to_json() | ||||||
) | ||||||
return make_response(jsonify(tasks_response), 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 get_one_task(task_id): | ||||||
task = validate_object("task", task_id) | ||||||
|
||||||
response = { "task": task.to_json()} | ||||||
|
||||||
return make_response(response, 200) | ||||||
|
||||||
|
||||||
@tasks_bp.route("/<task_id>", methods=["PUT"]) | ||||||
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 update_one_task(task_id): | ||||||
|
||||||
task = validate_object("task", task_id) | ||||||
|
||||||
request_body = request.get_json() | ||||||
|
||||||
task.update(request_body) | ||||||
|
||||||
db.session.commit() | ||||||
|
||||||
response = { "task": task.to_json()} | ||||||
return make_response(response, 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_one_task(task_id): | ||||||
|
||||||
task = validate_object("task", task_id) | ||||||
|
||||||
db.session.delete(task) | ||||||
db.session.commit() | ||||||
|
||||||
return make_response(jsonify({"details" : f"Task {task.task_id} \"{task.title}\" successfully deleted"})) | ||||||
|
||||||
|
||||||
@tasks_bp.route("<task_id>/mark_complete", 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_task_completed(task_id): | ||||||
task = validate_object("task", task_id) | ||||||
|
||||||
task.is_complete = True | ||||||
task.completed_at = datetime.now() | ||||||
|
||||||
db.session.commit() | ||||||
|
||||||
URL = "https://slack.com/api/chat.postMessage" | ||||||
PARAMS = {'text' : f"Someone just completed the task {task.title}", | ||||||
'channel' : 'task-notifications'} | ||||||
HEADERS = {'Authorization' : f'Bearer {os.environ.get("TASKLIST_BOT_KEY")}'} | ||||||
r = requests.get(url = URL, params = PARAMS, headers = HEADERS) | ||||||
Comment on lines
+210
to
+214
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 make a good helper function |
||||||
|
||||||
response = { "task": task.to_json()} | ||||||
|
||||||
return response, 200 | ||||||
|
||||||
|
||||||
@tasks_bp.route("<task_id>/mark_incomplete", methods=["PATCH"]) | ||||||
def mark_task_incompleted(task_id): | ||||||
task = validate_object("task", task_id) | ||||||
|
||||||
task.is_complete = False | ||||||
task.completed_at = None | ||||||
Comment on lines
+225
to
+226
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. we could make this into an instance method |
||||||
|
||||||
db.session.commit() | ||||||
|
||||||
response = { "task": task.to_json()} | ||||||
|
||||||
return response, 200 | ||||||
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 consistency with your return statements here, so that users can use your API in a predictable fashion
Suggested change
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
Comment on lines
+233
to
+236
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
|
||||||
|
||||||
def validate_object(object_type, object_id): | ||||||
|
||||||
try: | ||||||
object_id = int(object_id) | ||||||
except: | ||||||
abort(make_response({"message":f"{object_type} {object_id} invalid"}, 400)) | ||||||
|
||||||
if object_type == "task": | ||||||
item = Task.query.get(object_id) | ||||||
elif object_type == "goal": | ||||||
item = Goal.query.get(object_id) | ||||||
|
||||||
if not item: | ||||||
abort(make_response({"message":f"{object_type} {object_id} not found"}, 404)) | ||||||
|
||||||
return item | ||||||
Comment on lines
+238
to
+253
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 move this into its own file for helper functions |
||||||
|
||||||
|
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.
great job! If you are curious, check out
backref
attribute in SQLAlchemy to save you a line of code here!https://docs.sqlalchemy.org/en/14/orm/backref.html