-
Notifications
You must be signed in to change notification settings - Fork 111
Otters - Jodi Denney #104
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?
Otters - Jodi Denney #104
Changes from all commits
9de70fe
46223e5
b1898db
12e5980
c5a894b
54741c4
a3dfba8
b4af170
dfa932b
bc85418
dd77d83
0afdebc
fa366d0
1a4fbd5
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,121 @@ | ||
|
||
from flask import Blueprint, jsonify, make_response, request, abort | ||
from app import db | ||
from app.models.goal import Goal | ||
from app.models.task import Task | ||
from datetime import datetime | ||
import os | ||
from app.tasks_routes import validate_task_id | ||
|
||
goals_bp = Blueprint("goal_bp", __name__, url_prefix="/goals") | ||
|
||
|
||
def validate_goal_id(goal_id): | ||
try: | ||
id = int(goal_id) | ||
except: | ||
abort(make_response( | ||
{"message": f"goal {goal_id} invalid. Must be numerical"}, 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=["GET"]) | ||
def get_goals(): | ||
title_query = request.args.get("title") | ||
if title_query: | ||
goals = Goal.query.filter_by(title=title_query) | ||
else: | ||
goals = Goal.query.all() | ||
print(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. Remember to remove your print statements when you submit 😉 |
||
goal_response = [] | ||
for goal in goals: | ||
goal_response.append(goal.to_json()) | ||
return jsonify(goal_response) | ||
|
||
|
||
@goals_bp.route("/<goal_id>", methods=["GET"]) | ||
def get_single_goal(goal_id): | ||
goal = validate_goal_id(goal_id) | ||
return{"goal": goal.to_json()} | ||
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. Beautiful use of the Additionally, remember to give it an explicit status code! |
||
|
||
|
||
@goals_bp.route("", methods=["POST"]) | ||
def create_goal(): | ||
request_body = request.get_json() | ||
try: | ||
new_goal = Goal(title=request_body["title"]) | ||
except KeyError: | ||
return make_response({"details": "Invalid data"}, 400) | ||
|
||
db.session.add(new_goal) | ||
db.session.commit() | ||
|
||
return {"goal": new_goal.to_json()}, 201 | ||
|
||
|
||
@goals_bp.route("/<goal_id>", methods=["PUT"]) | ||
def update_goal(goal_id): | ||
found_goal = validate_goal_id(goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
found_goal.title = request_body["title"] | ||
|
||
db.session.commit() | ||
|
||
return {"goal": found_goal.to_json()}, 200 | ||
|
||
|
||
@goals_bp.route("/<goal_id>", methods=["DELETE"]) | ||
def delete_goal(goal_id): | ||
found_goal = validate_goal_id(goal_id) | ||
|
||
db.session.delete(found_goal) | ||
db.session.commit() | ||
|
||
return jsonify({"details": f'Goal {found_goal.goal_id} "{found_goal.title}" successfully deleted'}) | ||
|
||
|
||
@ goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
def add_tasks(goal_id): | ||
goal = validate_goal_id(goal_id) | ||
request_body = request.get_json() | ||
task_ids = request_body["task_ids"] | ||
|
||
for task_id in task_ids: | ||
valid_task = validate_task_id(task_id) | ||
goal.tasks.append(valid_task) | ||
# tasks = [] | ||
# for id in task_ids: | ||
# tasks.append(validate_task_id(id)) | ||
# for task in tasks: | ||
# task.goal_id = goal.goal_id | ||
|
||
Comment on lines
+94
to
+99
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 remember to remove commented out code! |
||
db.session.commit() | ||
return jsonify({"id": goal.goal_id, | ||
"task_ids": task_ids}) | ||
|
||
|
||
@ goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
def get_tasks(goal_id): | ||
goal = validate_goal_id(goal_id) | ||
tasks = [] | ||
|
||
for task in goal.tasks: | ||
tasks.append(({ | ||
"id": task.task_id, | ||
"title": task.title, | ||
"description": task.description, | ||
"is_complete": bool(task.completed_at)})) | ||
if task.goal_id: | ||
tasks[-1]["goal_id"] = task.goal_id | ||
|
||
return jsonify({"id": goal.goal_id, | ||
"title": goal.title, | ||
"tasks": tasks}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,10 @@ | |
|
||
|
||
class Goal(db.Model): | ||
goal_id = db.Column(db.Integer, primary_key=True) | ||
goal_id = db.Column(db.Integer, primary_key=True, autoincrement=True) | ||
title = db.Column(db.String) | ||
tasks = db.relationship("Task", back_populates="goal") | ||
|
||
def to_json(self): | ||
return {"id": self.goal_id, | ||
"title": self.title} | ||
Comment on lines
+9
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. Great instance method! 🎉 |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
from flask import Blueprint, jsonify, make_response, request, abort | ||
from app import db | ||
from app.models.task import Task | ||
from datetime import datetime | ||
import sys | ||
import os | ||
import requests | ||
from dotenv import load_dotenv | ||
load_dotenv() | ||
|
||
tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") | ||
|
||
SLACK_TOKEN = os.environ.get("SLACK_TOKEN") | ||
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 job using a constant to call the token from your env file! ✨ |
||
|
||
|
||
def validate_task_id(task_id): | ||
try: | ||
task_id = int(task_id) | ||
except: | ||
abort(make_response( | ||
{"message": f"Task {task_id} invalid. Must be numerical"}, 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
+16
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. Love to see this validation helper method up top! Great use of try/except and appropriate status codes as well. |
||
|
||
|
||
@tasks_bp.route("", methods=["GET"]) | ||
def get_tasks(): | ||
sort_query = request.args.get("sort") | ||
title_query = request.args.get("title") | ||
if title_query: | ||
tasks = Task.query.filter_by(title=title_query) | ||
elif sort_query == "desc": | ||
tasks = Task.query.order_by(Task.title.desc()).all() | ||
elif sort_query == "asc": | ||
tasks = Task.query.order_by(Task.title.asc()).all() | ||
else: | ||
tasks = Task.query.all() | ||
Comment on lines
+35
to
+42
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 sorting logic with One nit: in Python, the scope of |
||
tasks_response = [] | ||
for task in tasks: | ||
tasks_response.append({ | ||
"id": task.task_id, | ||
"title": task.title, | ||
"description": task.description, | ||
"is_complete": bool(task.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. Excellent use of the boolean casting here for |
||
return jsonify(tasks_response) | ||
|
||
|
||
@tasks_bp.route("/<task_id>", methods=["GET"]) | ||
def get_single_task(task_id): | ||
task = validate_task_id(task_id) | ||
task_reply = {"task": { | ||
"id": task.task_id, | ||
"title": task.title, | ||
"description": task.description, | ||
"is_complete": bool(task.completed_at)}} | ||
if task.goal_id: | ||
task_reply["task"]["goal_id"] = task.goal_id | ||
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. Love the efficiency in using a conditional to check if a key exists and then appending |
||
return task_reply | ||
|
||
|
||
@tasks_bp.route("", methods=["POST"]) | ||
def create_task(): | ||
request_body = request.get_json() | ||
try: | ||
new_task = Task(title=request_body["title"], | ||
description=request_body["description"]) | ||
except KeyError: | ||
return {"details": "Invalid data"}, 400 | ||
|
||
if "completed_at" in request_body: | ||
new_task.completed_at = request_body["completed_at"] | ||
|
||
db.session.add(new_task) | ||
db.session.commit() | ||
|
||
return {"task": { | ||
"id": new_task.task_id, | ||
"title": new_task.title, | ||
"description": new_task.description, | ||
"is_complete": bool(new_task.completed_at)}}, 201 | ||
|
||
|
||
@tasks_bp.route("/<task_id>", methods=["PUT"]) | ||
def update_task(task_id): | ||
found_task = validate_task_id(task_id) | ||
|
||
request_body = request.get_json() | ||
|
||
found_task.title = request_body["title"] | ||
found_task.description = request_body["description"] | ||
if "completed_at" in request_body: | ||
found_task.completed_at = request_body["completed_at"] | ||
db.session.commit() | ||
|
||
return {"task": | ||
{"id": found_task.task_id, | ||
"title": found_task.title, | ||
"description": found_task.description, | ||
"is_complete": bool(found_task.completed_at)}}, 200 | ||
|
||
|
||
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||
def update_task_status(task_id): | ||
found_task = validate_task_id(task_id) | ||
|
||
found_task.completed_at = datetime.now() | ||
|
||
db.session.commit() | ||
|
||
message = f"Someone just completed task {found_task.title}" | ||
|
||
headers = {"Authorization": "Bearer " + SLACK_TOKEN} | ||
params = {"channel": "task-notifications", "text": message} | ||
|
||
requests.post('https://slack.com/api/chat.postMessage', | ||
data=params, headers=headers) | ||
|
||
return {"task": | ||
{"id": found_task.task_id, | ||
"title": found_task.title, | ||
"description": found_task.description, | ||
"is_complete": bool(found_task.completed_at)}}, 200 | ||
|
||
|
||
@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||
def incomplete_task_status(task_id): | ||
found_task = validate_task_id(task_id) | ||
|
||
found_task.completed_at = None | ||
|
||
db.session.commit() | ||
|
||
return {"task": | ||
{"id": found_task.task_id, | ||
"title": found_task.title, | ||
"description": found_task.description, | ||
"is_complete": bool(found_task.completed_at)}}, 200 | ||
Comment on lines
+138
to
+142
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 return this kind of response four times in this code and a great place to DRY up our code! |
||
|
||
|
||
@tasks_bp.route("/<task_id>", methods=["DELETE"]) | ||
def delete_task(task_id): | ||
found_task = validate_task_id(task_id) | ||
|
||
db.session.delete(found_task) | ||
db.session.commit() | ||
|
||
return jsonify({"details": f'Task {found_task.task_id} "{found_task.title}" successfully deleted'}) |
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.
Nice helper method here! This code is also very similar to
validate_task_id
intask_routes
file -- could we modify this code to be able to work for both Models? 🤔