-
Notifications
You must be signed in to change notification settings - Fork 146
Panthers - Kallie and Billie resubmission #140
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
5d4806c
f4f1bb6
a7a52e0
8d10b66
2ac53c1
9f75cea
bd27fe9
601fd08
bf97c99
09302f2
f4459f7
ee050bc
261b2fa
908a61a
9dd82c4
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,110 @@ | ||
from app import db | ||
from app.models.goal import Goal | ||
from flask import Blueprint,jsonify,abort,make_response,request | ||
from app.models.task import Task | ||
|
||
goals_bp = Blueprint('goals_bp', __name__, url_prefix='/goals') | ||
GOAL_ID_PREFIX = '/<goal_id>' | ||
|
||
def validate_goal(goal_id): | ||
try: | ||
goal_id = int(goal_id) | ||
except: | ||
abort(make_response({"message":f"Goal {goal_id} invalid"}, 400)) | ||
|
||
goal = Goal.query.get(goal_id) | ||
|
||
if not goal: | ||
abort(make_response({"message":f"Goal {goal_id} not found"}, 404)) | ||
|
||
return goal | ||
|
||
@goals_bp.route("", methods=["POST"]) | ||
def create_goal(): | ||
request_body = request.get_json() | ||
|
||
if "title" not in request_body: | ||
return make_response({"details": "Invalid data"}, 400) | ||
|
||
new_goal = Goal(title = request_body["title"]) | ||
|
||
db.session.add(new_goal) | ||
db.session.commit() | ||
|
||
return make_response({"goal":new_goal.to_dict()}, 201) | ||
Comment on lines
+22
to
+34
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. 👍 Great work using
https://flask.palletsprojects.com/en/1.1.x/quickstart/#apis-with-json We can apply this same comment to the rest of this project. |
||
|
||
@goals_bp.route("", methods=["GET"]) | ||
def get_goals(): | ||
goal_query = Goal.query | ||
goals = goal_query.all() | ||
|
||
goals_response = [] | ||
for goal in goals: | ||
goals_response.append({ | ||
"id": goal.goal_id, | ||
"title": goal.title, | ||
}) | ||
|
||
return make_response(jsonify(goals_response), 200) | ||
Comment on lines
+36
to
+48
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! We can use list comprehension to build goals_response = [goal.to_dict() for goal in goals]
return jsonify(goals_response), 200 |
||
@goals_bp.route(GOAL_ID_PREFIX, methods=["GET"]) | ||
def get_one_goal(goal_id): | ||
goal = validate_goal(goal_id) | ||
goal = Goal.query.get(goal_id) | ||
|
||
return {"goal": goal.to_dict()} | ||
Comment on lines
+49
to
+54
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. 👍 |
||
|
||
@goals_bp.route(GOAL_ID_PREFIX,methods=['PUT']) | ||
def update_task(goal_id): | ||
request_body = request.get_json() | ||
if "title" not in request_body: | ||
return make_response("Invalid Request, Goal Must Have Title", 400) | ||
|
||
goal = validate_goal(goal_id) | ||
goal = Goal.query.get(goal_id) | ||
|
||
goal.title = request_body["title"] | ||
|
||
db.session.commit() | ||
return make_response({"goal":goal.to_dict()}, 200) | ||
Comment on lines
+56
to
+68
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. 👍 |
||
|
||
@goals_bp.route(GOAL_ID_PREFIX, methods=['DELETE']) | ||
def delete_goal(goal_id): | ||
goal = validate_goal(goal_id) | ||
goal = Goal.query.get(goal_id) | ||
|
||
db.session.delete(goal) | ||
db.session.commit() | ||
|
||
return make_response({"details": f'Goal {goal_id} "{goal.title}" successfully deleted'}, 200) | ||
Comment on lines
+70
to
+78
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. 👍 |
||
|
||
|
||
@goals_bp.route(GOAL_ID_PREFIX + "/tasks", methods=['GET']) | ||
def read_tasks (goal_id): | ||
goal = validate_goal(goal_id) | ||
goal = Goal.query.get(goal_id) | ||
|
||
return_body = goal.to_dict() | ||
return_body["tasks"] = [task.to_dict_in_goal() for task in goal.tasks] | ||
|
||
return make_response(jsonify(return_body), 200) | ||
Comment on lines
+81
to
+89
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 building the |
||
|
||
|
||
@goals_bp.route(GOAL_ID_PREFIX + "/tasks", methods=['POST']) | ||
def add_tasks_to_goal(goal_id): | ||
goal = validate_goal(goal_id) | ||
goal = Goal.query.get(goal_id) | ||
|
||
request_body = request.get_json() | ||
tasks_to_assign = request_body["task_ids"] | ||
|
||
for task_id in tasks_to_assign: | ||
task = Task.query.get(task_id) | ||
task.goal_id = goal.goal_id | ||
|
||
db.session.commit() | ||
|
||
return_body = {} | ||
return_body["id"] = goal.goal_id | ||
return_body["task_ids"] = tasks_to_assign | ||
|
||
return make_response(return_body, 200) | ||
Comment on lines
+92
to
+110
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. Great work utilizing the relationship attribute 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 can DRY up our code by building the code directly: return_body = {
"id": goal.goal_id,
"task_id": tasks_to_assign
}
return return_body, 200 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,12 @@ | ||
from app import db | ||
|
||
|
||
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_dict(self): | ||
return { | ||
"id": self.goal_id, | ||
"title": self.title | ||
} | ||
Comment on lines
+5
to
+12
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. 👍 Good work setting up the It wasn't mentioned in the README but whenever we're designing models/tables we should also consider whether each column should allow nullable values. In this case, is storing a goal with an empty title useful to have in our database? Or should we require every goal to have a title? If the latter, we can add Also nice helper function! |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,27 @@ | |
|
||
|
||
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) | ||
is_completed = db.Column(db.Boolean, default=False) | ||
goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'), nullable=True) | ||
goal = db.relationship("Goal", back_populates="tasks") | ||
Comment on lines
+5
to
+11
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. 👍 Good work setting up the Task model. The same comments about using |
||
|
||
def to_dict(self): | ||
return { | ||
"id": self.task_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": self.is_completed | ||
} | ||
|
||
def to_dict_in_goal(self): | ||
return { | ||
"id": self.task_id, | ||
"goal_id": self.goal_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": self.is_completed | ||
} | ||
Comment on lines
+13
to
+28
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 helper functions! We can condense these two functions into one by building an initial dictionary and then checking if a goal_id exists for that particular task. def to_dict(self):
task = {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": self.is_completed
}
if self.goal_id:
task['goal_id'] = self.goal_id
return task |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import datetime | ||
from app import db | ||
from app.models.task import Task | ||
from flask import Blueprint,jsonify,abort,make_response,request | ||
import requests | ||
from datetime import datetime | ||
import os | ||
from dotenv import load_dotenv | ||
|
||
tasks_bp = Blueprint('tasks_bp', __name__, url_prefix='/tasks') | ||
TASK_ID_PREFIX = '/<task_id>' | ||
|
||
def validate_task(task_id): | ||
try: | ||
task_id = int(task_id) | ||
except: | ||
abort(make_response({"message":f"Task {task_id} invalid"}, 400)) | ||
|
||
task = Task.query.get(task_id) | ||
|
||
if not task: | ||
abort(make_response({"message":f"Task {task_id} not found"}, 404)) | ||
|
||
return task | ||
Comment on lines
+13
to
+24
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 helper function! Notice how def validate_task(cls, id):
try:
obj_id = int(id)
except:
abort(make_response({"message":f"{cls.__name__} {id} invalid"}, 400))
obj = cls.query.get(id)
if not obj:
abort(make_response({"message":f"{cls.__name__}{id} not found"}, 404))
return obj |
||
|
||
@tasks_bp.route("", methods=["POST"]) | ||
def create_task(): | ||
request_body = request.get_json() | ||
|
||
if "title" not in request_body or "description" not in request_body: | ||
return make_response({"details": "Invalid data"}, 400) | ||
|
||
new_task = Task( | ||
title = request_body["title"], | ||
description = request_body["description"], | ||
) | ||
|
||
db.session.add(new_task) | ||
db.session.commit() | ||
|
||
return make_response({"task":new_task.to_dict()}, 201) | ||
Comment on lines
+26
to
+41
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. 👍 |
||
|
||
@tasks_bp.route(TASK_ID_PREFIX,methods=['PUT']) | ||
def update_task(task_id): | ||
request_body = request.get_json() | ||
if "title" not in request_body or "description" not in request_body: | ||
return make_response("Invalid Request, Title & Description Can't Be Empty", 400) | ||
|
||
task = validate_task(task_id) | ||
task = Task.query.get(task_id) | ||
|
||
task.title = request_body["title"] | ||
task.description = request_body["description"] | ||
|
||
db.session.commit() | ||
return make_response({"task":task.to_dict()}, 200) | ||
Comment on lines
+43
to
+56
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 use of I've mentioned it before, but we don't need to use |
||
|
||
|
||
@tasks_bp.route(TASK_ID_PREFIX, methods=["GET"]) | ||
# GET /task/id | ||
def handle_task(task_id): | ||
# Query our db to grab the task that has the id we want: | ||
task = validate_task(task_id) | ||
task = Task.query.get(task_id) | ||
|
||
if task.goal_id is not None: | ||
return{"task": task.to_dict_in_goal()} | ||
else: | ||
return {"task": task.to_dict()} | ||
Comment on lines
+59
to
+69
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. 👍 See comment in with that refactor we can reduce these lines to: return {"task": task.to_dict()} |
||
|
||
|
||
@tasks_bp.route('',methods=['GET']) | ||
def get_task(): | ||
task_query = Task.query | ||
|
||
sort_query = request.args.get("sort") | ||
if sort_query: | ||
task_response = [] | ||
tasks = task_query.all() | ||
for task in tasks: | ||
task_response.append(task.to_dict()) | ||
Comment on lines
+79
to
+81
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 can use list comprehension here task_response = [ task.to_dict() for task in tasks] |
||
|
||
task_titles = [] | ||
|
||
for task in task_response: | ||
for key, value in task.items(): | ||
if key == "title": | ||
task_titles.append(value) | ||
|
||
response_body = [] | ||
if sort_query == "asc": | ||
sorted_tasks = sorted(task_titles) | ||
if sort_query == "desc": | ||
sorted_tasks = sorted(task_titles, reverse=True) | ||
while len(response_body) < len(task_titles): | ||
for task in task_response: | ||
if len(sorted_tasks) == 0: | ||
break | ||
if task["title"] == sorted_tasks[0]: | ||
response_body.append(task) | ||
sorted_tasks.pop(0) | ||
return make_response(jsonify(response_body), 200) | ||
Comment on lines
+77
to
+102
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. The goal here was to use SQLAlchemy's sort commands to do the sorting for us rather than using python's built-in We can do that by using : tasks = task_query.order_by(Task.title.desc())
tasks = task_query.order_by(Task.title.asc()) 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. Just that line should get rid of all extra the logic to sort |
||
|
||
descripiton_query = request.args.get("description") | ||
if descripiton_query: | ||
task_query = task_query.filter_by(description = descripiton_query) | ||
|
||
title_query = request.args.get("title") | ||
if title_query: | ||
task_query = task_query.filter_by(name = title_query) | ||
|
||
is_complete_query = request.args.get("is_completed") | ||
if is_complete_query: | ||
task_query = task_query.filter_by(is_completed= is_complete_query) | ||
Comment on lines
+104
to
+114
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 adding extra queries! |
||
|
||
tasks = task_query.all() | ||
|
||
tasks_response = [] | ||
for task in tasks: | ||
tasks_response.append({ | ||
"id": task.task_id, | ||
"title": task.title, | ||
"is_complete": task.is_completed, | ||
"description": task.description | ||
}) | ||
Comment on lines
+118
to
+125
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 can use the tasks_response = [tasks.to_dict() for task in tasks] |
||
|
||
return jsonify(tasks_response) | ||
|
||
@tasks_bp.route(TASK_ID_PREFIX,methods=['DELETE']) | ||
def delete_task(task_id): | ||
task = validate_task(task_id) | ||
task = Task.query.get(task_id) | ||
|
||
db.session.delete(task) | ||
db.session.commit() | ||
|
||
return make_response({"details": f'Task {task_id} "{task.title}" successfully deleted'}, 200) | ||
Comment on lines
+129
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. 👍 |
||
|
||
@tasks_bp.route(TASK_ID_PREFIX + '/mark_complete', methods=['PATCH']) | ||
def update_task_complete(task_id): | ||
task = validate_task(task_id) | ||
date_time_assign = datetime.now() | ||
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 didn't have to put this in a variable as |
||
if task.is_completed == False: | ||
task.completed_at = date_time_assign | ||
task.is_completed = True | ||
task_response = {"task": task.to_dict()} | ||
db.session.commit() | ||
load_dotenv() | ||
|
||
URL = "https://slack.com/api/chat.postMessage" | ||
|
||
payload={"channel":"slack-bot-test-channel", | ||
"text": f"Someone just completed the task {task.title}"} | ||
|
||
headers = { | ||
"Authorization": os.environ.get('SLACK_TOKEN') | ||
} | ||
|
||
|
||
requests.post(URL, data=payload, headers=headers) | ||
Comment on lines
+150
to
+160
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 setting up the slackbot. We can move this logic into a helper function just to follow the single responsibility principle. Moving this logic into a helper function also allows the opportunity for other routes to make a slack post! |
||
return jsonify(task_response),200 | ||
|
||
@tasks_bp.route(TASK_ID_PREFIX + '/mark_incomplete', methods=['PATCH']) | ||
def update_task_incomplete(task_id): | ||
task = validate_task(task_id) | ||
task = Task.query.get(task_id) | ||
|
||
task.is_completed= False | ||
task.completed_at = None | ||
|
||
db.session.commit() | ||
return make_response({"task":task.to_dict()}, 200) | ||
Comment on lines
+163
to
+172
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. 👍 |
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 helper function!