-
Notifications
You must be signed in to change notification settings - Fork 146
Tigers - Paje B #128
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?
Tigers - Paje B #128
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 |
---|---|---|
@@ -0,0 +1,92 @@ | ||
from flask import Blueprint, jsonify, request, make_response, abort | ||
from app.models.task import Task | ||
from app.models.goal import Goal | ||
from app.routes import validate_model_id | ||
from sqlalchemy import desc, asc | ||
import datetime | ||
import requests | ||
import os | ||
from app import db | ||
|
||
goals_bp = Blueprint("goals", __name__, url_prefix="/goals") | ||
|
||
|
||
@goals_bp.route("", methods=["GET"]) | ||
def retrieve_goals(): | ||
goals = Goal.query.all() | ||
goals_response = [] | ||
|
||
for goal in goals: | ||
goals_response.append(goal.format_goal()) | ||
return jsonify(goals_response) | ||
|
||
|
||
@goals_bp.route("", methods=["POST"]) | ||
def create_goal(): | ||
request_body = request.get_json() | ||
|
||
if not request_body.get("title"): | ||
return {"details": "Invalid data"}, 400 | ||
|
||
new_goal = Goal(title=request_body["title"]) | ||
db.session.add(new_goal) | ||
db.session.commit() | ||
|
||
return {"goal": {"id": new_goal.goal_id, "title": new_goal.title}}, 201 | ||
|
||
|
||
@goals_bp.route("/<goal_id>", methods=["GET"]) | ||
def handle_goal(goal_id): | ||
goal = validate_model_id(Goal, goal_id) | ||
|
||
return {"goal": goal.format_goal()} | ||
|
||
|
||
@goals_bp.route("/<goal_id>", methods=["PUT"]) | ||
def update_goal(goal_id): | ||
goal = validate_model_id(Goal, goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
goal.title = request_body["title"] | ||
db.session.commit() | ||
|
||
return make_response({"goal": {"id": goal.goal_id, "title": goal.title}}) | ||
|
||
|
||
@goals_bp.route("/<goal_id>", methods=["DELETE"]) | ||
def delete_goal(goal_id): | ||
goal = validate_model_id(Goal, goal_id) | ||
|
||
db.session.delete(goal) | ||
db.session.commit() | ||
|
||
return make_response( | ||
{"details": f'Goal {goal_id} "{goal.title}" successfully deleted'} | ||
) | ||
|
||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
def add_task_to_goal(goal_id): | ||
goal = validate_model_id(Goal, goal_id) | ||
tasks_response = [] | ||
for task in Goal.query.get(goal_id).tasks: | ||
task.goal_id = goal.goal_id | ||
tasks_response.append(task.format_task_goal()) | ||
|
||
return {"id": goal.goal_id, "title": goal.title, "tasks": tasks_response} | ||
|
||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
def retrieve_tasks_from_goal(goal_id): | ||
goal = validate_model_id(Goal, goal_id) | ||
request_body = request.get_json() | ||
task_ids = [] | ||
for task_id in request_body["task_ids"]: | ||
task = validate_model_id(Task, task_id) | ||
task.goal_id = goal.goal_id | ||
task_ids.append(task.task_id) | ||
|
||
db.session.commit() | ||
|
||
return {"id": goal.goal_id, "task_ids": task_ids} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,26 @@ | |
|
||
|
||
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")) | ||
goal = db.relationship("Goal", back_populates="tasks") | ||
|
||
def format_task(self): | ||
return { | ||
"id": self.task_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": bool(self.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. I like this use of |
||
} | ||
|
||
def format_task_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
+12
to
+27
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. But this repetition between def format_task(self):
task_dict = {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": bool(self.completed_at),
}
if self.goal_id:
task_dict["goal_id"] = self.goal_id
return task_dict and avoid the repetition. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,147 @@ | ||
from flask import Blueprint | ||
from flask import Blueprint, jsonify, request, make_response, abort | ||
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. Minor file naming: While keeping the If there is some concern that This file would be kind of a grab bag of utility methods, which, while not ideal are very common. So I would call it |
||
from app.models.task import Task | ||
from sqlalchemy import desc, asc | ||
import datetime | ||
import requests | ||
import os | ||
from app import db | ||
|
||
|
||
def slackbot_call(message): | ||
PATH = "https://slack.com/api/chat.postMessage" | ||
parameters = {"channel": "task-notifications", "text": message} | ||
|
||
requests.post( | ||
PATH, | ||
params=parameters, | ||
headers={"Authorization": os.environ.get("SLACKBOT_API_KEY")}, | ||
) | ||
Comment on lines
+10
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. Nice helper function! |
||
|
||
|
||
def validate_model_id(cls, model_id): | ||
try: | ||
model_id = int(model_id) | ||
except ValueError: | ||
abort( | ||
make_response( | ||
f"{model_id} is an invalid input. Please input an integer.", 400 | ||
) | ||
) | ||
|
||
model = cls.query.get(model_id) | ||
if model: | ||
return model | ||
else: | ||
abort(make_response({"error": f"Item #{model_id} cannot be found"}, 404)) | ||
|
||
|
||
tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") | ||
|
||
|
||
@tasks_bp.route("", methods=["GET"]) | ||
def retrieve_tasks(): | ||
tasks = Task.query.all() | ||
tasks_response = [] | ||
sort_query = request.args.get("sort") | ||
|
||
if sort_query == "asc": | ||
tasks = Task.query.order_by(asc("title")) | ||
elif sort_query == "desc": | ||
tasks = Task.query.order_by(desc("title")) | ||
|
||
for task in tasks: | ||
tasks_response.append(task.format_task()) | ||
return jsonify(tasks_response) | ||
|
||
|
||
@tasks_bp.route("", methods=["POST"]) | ||
def create_task(): | ||
request_body = request.get_json() | ||
|
||
if not request_body.get("title") or not request_body.get("description"): | ||
return {"details": "Invalid data"}, 400 | ||
Comment on lines
+61
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. Good job using |
||
|
||
new_task = Task( | ||
title=request_body["title"], | ||
description=request_body["description"], | ||
completed_at=request_body.get("completed_at"), | ||
) | ||
db.session.add(new_task) | ||
db.session.commit() | ||
return {"task": new_task.format_task()}, 201 | ||
|
||
|
||
@tasks_bp.route("/<task_id>", methods=["GET"]) | ||
def handle_task(task_id): | ||
task = validate_model_id(Task, task_id) | ||
if task.goal_id: | ||
return {"task": task.format_task_goal()} | ||
else: | ||
return {"task": task.format_task()} | ||
|
||
|
||
@tasks_bp.route("/<task_id>", methods=["PUT"]) | ||
def update_task(task_id): | ||
task = validate_model_id(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") | ||
|
||
db.session.commit() | ||
|
||
return {"task": task.format_task()} | ||
|
||
|
||
@tasks_bp.route("/<task_id>", methods=["DELETE"]) | ||
def delete_task(task_id): | ||
task = validate_model_id(Task, task_id) | ||
|
||
db.session.delete(task) | ||
db.session.commit() | ||
|
||
return make_response( | ||
{"details": f'Task {task_id} "{task.title}" successfully deleted'} | ||
) | ||
Comment on lines
+105
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. Not really a need for 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 see a few other places where this is true, but I'll just point it out this once and not repeat myself. |
||
|
||
|
||
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||
def update_task_as_complete(task_id): | ||
task = validate_model_id(Task, task_id) | ||
task.completed_at = datetime.datetime.now() | ||
db.session.commit() | ||
|
||
slackbot_call(f"Someone just completed the task {task.title}") | ||
|
||
return make_response( | ||
{ | ||
"task": { | ||
"id": task.task_id, | ||
"title": task.title, | ||
"description": task.description, | ||
"is_complete": (bool(task.completed_at)), | ||
} | ||
Comment on lines
+120
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. I had a comment pointing out that This could justify a separate Either way, I still think the cleanest way I would implement is still handling in my suggested 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 use of |
||
} | ||
) | ||
|
||
|
||
@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||
def update_task_as_incomplete(task_id): | ||
task = validate_model_id(Task, task_id) | ||
|
||
task.completed_at = None | ||
|
||
db.session.commit() | ||
|
||
return make_response( | ||
{ | ||
"task": { | ||
"id": task.task_id, | ||
"title": task.title, | ||
"description": task.description, | ||
"is_complete": (bool(task.completed_at)), | ||
} | ||
} | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Generic single-database configuration. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# A generic, single database configuration. | ||
|
||
[alembic] | ||
# template used to generate migration files | ||
# file_template = %%(rev)s_%%(slug)s | ||
|
||
# set to 'true' to run the environment during | ||
# the 'revision' command, regardless of autogenerate | ||
# revision_environment = false | ||
|
||
|
||
# Logging configuration | ||
[loggers] | ||
keys = root,sqlalchemy,alembic | ||
|
||
[handlers] | ||
keys = console | ||
|
||
[formatters] | ||
keys = generic | ||
|
||
[logger_root] | ||
level = WARN | ||
handlers = console | ||
qualname = | ||
|
||
[logger_sqlalchemy] | ||
level = WARN | ||
handlers = | ||
qualname = sqlalchemy.engine | ||
|
||
[logger_alembic] | ||
level = INFO | ||
handlers = | ||
qualname = alembic | ||
|
||
[handler_console] | ||
class = StreamHandler | ||
args = (sys.stderr,) | ||
level = NOTSET | ||
formatter = generic | ||
|
||
[formatter_generic] | ||
format = %(levelname)-5.5s [%(name)s] %(message)s | ||
datefmt = %H:%M:%S |
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.
Like my comment on missed
format_task
opportunities,format_goal
could have been used here.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.
And other places, but I'll not repeat myself.