-
Notifications
You must be signed in to change notification settings - Fork 111
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
Project complete and deployed #105
base: master
Are you sure you want to change the base?
Changes from all commits
129194d
bbd4baf
623aae4
e7a35b7
5325a46
a002b0f
c935668
e79e03d
73451b8
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 |
---|---|---|
@@ -0,0 +1,123 @@ | ||
from flask import Blueprint, make_response, request, jsonify, abort | ||
from app.models.goal import Goal | ||
from app import db | ||
from flask import request | ||
from datetime import datetime | ||
from app.models.task import Task | ||
# datetime.datetime.utcnow() | ||
|
||
goal_bp = Blueprint("goal_bp", __name__, url_prefix = "/goals") | ||
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. To keep your project organized, you can create a routes directory under the app directory. In the routes directory, you can add goal_routes.py and you can rename routes.py to task_routes.py |
||
|
||
|
||
def validate_goal(id_): | ||
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. Curious about |
||
try: | ||
id_ = int(id_) | ||
except: | ||
abort(make_response({"details": "Invalid data"}, 400)) | ||
|
||
|
||
|
||
goal = Goal.query.get(id_) | ||
# gets the whole row in db with that particular id | ||
|
||
if not goal: | ||
abort(make_response({"message": f"Goal {id_} not found"}, 404)) | ||
|
||
return goal | ||
|
||
@goal_bp.route("", methods=["POST"]) | ||
def create_goal(): | ||
|
||
request_body = request.get_json() | ||
|
||
try: | ||
new_goal = Goal.create(request_body) | ||
|
||
except KeyError: | ||
return abort(make_response({"details": "Invalid data"}, 400)) | ||
Comment on lines
+33
to
+37
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 error handling here! We can't assume that clients will send valid requests to our API so we have to anticipate a scenario where the req doesn't have the keys your method will need to access. |
||
|
||
|
||
db.session.add(new_goal) | ||
db.session.commit() | ||
|
||
return make_response(jsonify(new_goal.to_dict()), 201) | ||
|
||
|
||
|
||
@goal_bp.route("", methods=["GET"]) | ||
def get_goals(): | ||
goals = Goal.query.all() | ||
goals_response = [] | ||
for goal in goals: | ||
goals_response.append( | ||
{ | ||
"id": goal.id, | ||
"title": goal.title | ||
} | ||
) | ||
Comment on lines
+51
to
+57
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. If you use a to_json() method on each goal, then you could use list comprehension here to keep your route method concise. Something like: goals_response = [goal.to_json() for goal in goals] |
||
|
||
return jsonify(goals_response), 200 | ||
|
||
@goal_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
def get_task_with_one_goal(goal_id): | ||
goal = validate_goal(goal_id) | ||
|
||
tasks_response = [] | ||
for task in goal.tasks: | ||
tasks_response.append(Task.to_json(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. Quick thought here about to_json() is an instance method of the Task class (since it's not decorated with tasks_response.append(task.to_json()) |
||
# goal["tasks"] = tasks_response | ||
|
||
return jsonify({"id": goal.id, "title": goal.title, "tasks": tasks_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. Consider using your Goal instance method to_dict() which already sets id and title. You could add conditional logic in to_dict() to set tasks too. Something like: def to_dict(self):
goal_dict = {
"id": self.id,
"title": self.title
}
if self.tasks:
goal_dict["tasks"] = self.tasks
return goal_dict |
||
# return jsonify(goal), 200 | ||
|
||
@goal_bp.route("/<id>", methods=["GET"]) | ||
def get_one_goal(id): | ||
goal = validate_goal(id) | ||
|
||
return jsonify(goal.to_dict()), 200 | ||
|
||
|
||
@goal_bp.route("/<id>", methods=["PUT"]) | ||
def update_goal(id): | ||
goal = validate_goal(id) | ||
|
||
request_body = request.get_json() | ||
|
||
goal.update(request_body) | ||
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 can add error handling here like you did in your POST method above to handle a scenario where the request_body might not have "title" |
||
|
||
db.session.commit() | ||
|
||
return jsonify(goal.to_dict()), 200 | ||
|
||
|
||
@goal_bp.route("/<id>", methods=["DELETE"]) | ||
def delete_goal(id): | ||
goal = validate_goal(id) | ||
|
||
db.session.delete(goal) | ||
db.session.commit() | ||
|
||
return make_response(jsonify({"details": f"Goal {id} \"{goal.title}\" successfully deleted"}), 200) | ||
|
||
@goal_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
def create_task_with_goal(goal_id): | ||
goal = validate_goal(goal_id) | ||
request_body = request.get_json() | ||
# new_task = Task.create(request_body) | ||
task_ids = request_body["task_ids"] | ||
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 can add error handling here to ensure that request_body has "task_ids" and implement some logic to return an error if task_ids aren't there. |
||
|
||
for task_id in task_ids: | ||
task = Task.query.get(task_id) | ||
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 could use your validate_task() method here |
||
task.goal_id = goal_id | ||
|
||
db.session.commit() | ||
|
||
goal_response = { | ||
"id": goal.id, | ||
"task_ids": task_ids | ||
} | ||
|
||
# return make_response(jsonify({"id": goal.id, "task_ids": task_ids}), 200) | ||
return make_response(jsonify(goal_response), 200) | ||
Comment on lines
+115
to
+121
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 can use your to_dict function in the Goal class directly in line 121, this would enable you to delete lines 115-118. Then line 121 would look like: return make_response(jsonify(goal.to_json()), 200) |
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,28 @@ | ||
from app import db | ||
|
||
|
||
|
||
class Goal(db.Model): | ||
goal_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) | ||
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 work using nullable=False |
||
tasks = db.relationship("Task", back_populates="goal", lazy=True) | ||
|
||
|
||
def to_dict(self): | ||
return {"goal": { | ||
"id": self.id, | ||
"title": self.title | ||
} | ||
} | ||
|
||
|
||
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,51 @@ | ||
|
||
from app import db | ||
# import datetime | ||
|
||
|
||
class Task(db.Model): | ||
task_id = db.Column(db.Integer, primary_key=True) | ||
title = db.Column(db.String) | ||
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. Consider adding nullable=False here so your tasks have titles |
||
description = db.Column(db.String) | ||
completed_at = db.Column(db.DateTime, nullable = True) | ||
goal_id = db.Column(db.Integer, db.ForeignKey('goal.id'), nullable=True) | ||
goal = db.relationship("Goal", back_populates="tasks") | ||
|
||
def to_json(self): | ||
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. 👍 |
||
# change name to_dict(self): | ||
is_complete = False | ||
# if (self.completed_at is not None): | ||
if (self.completed_at is not None): | ||
is_complete = True | ||
task = { | ||
# "task_id": self.task_id, | ||
"id": self.task_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": is_complete | ||
} | ||
if self.goal: | ||
# task["goal"] = self.goal.title | ||
task["goal_id"] = self.goal.id | ||
return task | ||
|
||
def update(self, req_body): | ||
self.title = req_body["title"] | ||
self.description = req_body["description"] | ||
# self.completed_at = req_body["is_complete"] | ||
# check to see if this needs to be here or not | ||
|
||
@classmethod | ||
def create(cls, req_body): | ||
if "completed_at" in req_body: | ||
new_task = cls( | ||
title=req_body["title"], | ||
description=req_body["description"], | ||
completed_at=req_body["completed_at"] | ||
) | ||
else: | ||
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,146 @@ | ||
from flask import Blueprint | ||
from flask import Blueprint, make_response, request, jsonify, abort | ||
from app.models.task import Task | ||
from app import db | ||
import requests | ||
from datetime import datetime | ||
import os | ||
from dotenv import load_dotenv | ||
load_dotenv() | ||
|
||
# datetime.datetime.utcnow() | ||
|
||
tasks_bp = Blueprint("tasks_bp", __name__, url_prefix = "/tasks") | ||
|
||
|
||
def validate_task(id_): | ||
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 q here about |
||
try: | ||
id_ = int(id_) | ||
except: | ||
abort(make_response({"details": "Invalid data"}, 400)) | ||
|
||
|
||
|
||
task = Task.query.get(id_) | ||
# gets the whole row in db with that particular id | ||
|
||
if not task: | ||
abort(make_response({"message": f"Task {id_} not found"}, 404)) | ||
|
||
return task | ||
|
||
|
||
@tasks_bp.route("", methods=["POST"]) | ||
def create_task(): | ||
|
||
request_body = request.get_json() | ||
print(request_body) | ||
try: | ||
new_task = Task.create(request_body) | ||
# new_task = Task(title=request_body["title"], | ||
# description=request_body["description"]) | ||
except KeyError: | ||
return abort(make_response({"details": "Invalid data"}, 400)) | ||
|
||
|
||
db.session.add(new_task) | ||
db.session.commit() | ||
|
||
# return make_response(jsonify(f"Task {new_task.title} successfully created"), 201) | ||
return make_response(jsonify({"task": new_task.to_json()}), 201) | ||
|
||
|
||
@tasks_bp.route("/<tasks_id>/mark_complete", methods=["PATCH"]) | ||
def mark_task_complete(tasks_id): | ||
task = validate_task(tasks_id) | ||
|
||
# request_body = request.get_json() | ||
# task.completed_at = request_body["completed_at"] | ||
task.completed_at = datetime.utcnow() | ||
|
||
# request_body = request.get_json() | ||
# task.completed_at = response_body["completed_at"] | ||
# task.update(response_body) | ||
Comment on lines
+56
to
+62
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. Remember to remove comments to keep your code clean. It also helps us to avoid the sitch where something gets accidentally uncommented and the code executes when it shouldn't. |
||
|
||
db.session.commit() | ||
|
||
SLACK_BOT_POST_PATH = "https://slack.com/api/chat.postMessage" | ||
|
||
query_params = { | ||
"channel": "test-channel", | ||
"text": f"Someone just completed the task {task.title}" | ||
} | ||
|
||
headers = {"Authorization": os.environ.get("SLACK_BOT_KEY")} | ||
|
||
response_bot = requests.post(SLACK_BOT_POST_PATH, params=query_params, headers=headers) | ||
Comment on lines
+66
to
+75
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. Consider putting this into a helper method and then calling it here to keep the route concise. Using a helper method would also apply the SRP (single responsibility principle) to the route function. |
||
|
||
return make_response(jsonify({"task": task.to_json()}), 200) | ||
|
||
@tasks_bp.route("/<tasks_id>/mark_incomplete", methods=["PATCH"]) | ||
def mark_task_incomplete(tasks_id): | ||
task = validate_task(tasks_id) | ||
|
||
task.completed_at = None | ||
|
||
# request_body = request.get_json() | ||
# task.completed_at = response_body["completed_at"] | ||
# task.update(response_body) | ||
|
||
db.session.commit() | ||
|
||
return make_response(jsonify({"task": task.to_json()}), 200) | ||
|
||
|
||
@tasks_bp.route("", methods=["GET"]) | ||
def get_tasks(): | ||
title_param = request.args.get("title") | ||
|
||
sort_param = request.args.get("sort") | ||
|
||
if title_param: | ||
tasks = Task.query.filter_by(title=title_param) | ||
else: | ||
if sort_param == "desc": | ||
tasks = Task.query.order_by(Task.title.desc()).all() | ||
else: | ||
# tasks = Task.query.all() | ||
tasks = Task.query.order_by(Task.title.asc()).all() | ||
Comment on lines
+103
to
+107
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. with order_by() you don't need to use chain .all() to the end of this statement. |
||
|
||
|
||
task_response_body = [] | ||
for task in tasks: | ||
task_response_body.append(task.to_json()) | ||
Comment on lines
+111
to
+112
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 can use list comprehension here if you'd like. |
||
|
||
# [task.tojson() for task in tasks] | ||
|
||
return jsonify(task_response_body), 200 | ||
|
||
|
||
|
||
@tasks_bp.route("/<task_id>", methods=["GET"]) | ||
def read_one_task(task_id): | ||
task = validate_task(task_id) | ||
|
||
return jsonify({"task": task.to_json()}), 200 | ||
|
||
@tasks_bp.route("/<task_id>", methods=["PUT"]) | ||
def update_planet(task_id): | ||
task = validate_task(task_id) | ||
|
||
request_body = request.get_json() | ||
|
||
task.update(request_body) | ||
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. Consider adding some error handling here if request_body doesn't have "title" or "description" |
||
|
||
db.session.commit() | ||
|
||
return make_response(jsonify({"task": task.to_json()}), 200) | ||
|
||
@tasks_bp.route("/<task_id>", methods=["DELETE"]) | ||
def delete_task(task_id): | ||
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"}), 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.
Deleting comments like this can keep your code clean