-
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
Sharks - Morgan B. #101
base: master
Are you sure you want to change the base?
Sharks - Morgan B. #101
Changes from all commits
a94ea39
c68f44a
b02f9f6
6ea0485
c57d0e2
bdccaeb
943a557
3bb140e
54ede24
56359ac
85f7c5d
aa4b721
437e952
a9da764
2c15354
68966bc
21356dd
0338b53
02f3cae
2020c7d
45bf13b
464659b
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 |
---|---|---|
|
@@ -3,3 +3,36 @@ | |
|
||
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 to ensure every task requires a title. |
||
description = db.Column(db.String) | ||
completed_at = db.Column(db.DateTime, nullable=True) | ||
goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'), | ||
nullable=True) | ||
goal = db.relationship("Goal", back_populates="tasks") | ||
|
||
@classmethod | ||
def create(cls, req_body): | ||
new_task = cls( | ||
title=req_body["title"], | ||
description=req_body["description"], | ||
completed_at = req_body.get("completed_at") | ||
) | ||
return new_task | ||
|
||
def to_json(self): | ||
task_dict = { | ||
"id": self.task_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": True if self.completed_at else False | ||
} | ||
|
||
if self.goal: | ||
task_dict["goal_id"] = self.goal_id | ||
|
||
return task_dict | ||
Comment on lines
+27
to
+33
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(self, req_body): | ||
self.title = req_body["title"] | ||
self.description = req_body["description"] | ||
self.completed_at = req_body.get("completed_at") |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
from flask import Blueprint, jsonify, make_response, request, abort | ||
from app import db | ||
from app.models.goal import Goal | ||
from .helpers import validate_goal, validate_task | ||
|
||
goals_bp = Blueprint("goals", __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. Nice work creating two separate route files for each model and adding them to a routes directory |
||
|
||
@goals_bp.route("", methods=["POST"]) | ||
def create_goal(): | ||
request_body = request.get_json() | ||
|
||
if request_body.get("title"): | ||
new_goal = Goal.create(request_body) | ||
else: | ||
abort(make_response({"details": "Invalid data"}, 400)) | ||
Comment on lines
+12
to
+15
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 |
||
|
||
db.session.add(new_goal) | ||
db.session.commit() | ||
|
||
return make_response(jsonify({"goal": new_goal.to_json()}), 201) | ||
|
||
@goals_bp.route("", methods=["GET"]) | ||
def get_all_goals(): | ||
goals = Goal.query.all() | ||
|
||
goal_response_body = [] | ||
for goal in goals: | ||
goal_response_body.append(goal.to_json()) | ||
Comment on lines
+27
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. This works well and is readable, but if you'd like to incorporate list comprehensions into your code, you could write it like this:
|
||
|
||
return jsonify(goal_response_body), 200 | ||
|
||
@goals_bp.route("/<goal_id>", methods=["GET"]) | ||
def get_one_goal(goal_id): | ||
goal = validate_goal(goal_id) | ||
|
||
return jsonify({"goal": goal.to_json()}), 200 | ||
|
||
@goals_bp.route("/<goal_id>", methods=["PUT"]) | ||
def update_goal(goal_id): | ||
goal = validate_goal(goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
if request_body.get("title"): | ||
goal.update(request_body) | ||
else: | ||
abort(make_response({"details": "Invalid data"}, 400)) | ||
Comment on lines
+44
to
+47
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't assume that the client will always pass us a correctly formatted request so good job adding error handling here too |
||
|
||
db.session.commit() | ||
|
||
return jsonify({"goal": goal.to_json()}), 200 | ||
|
||
@goals_bp.route("/<goal_id>", methods=["DELETE"]) | ||
def delete_goal(goal_id): | ||
goal = validate_goal(goal_id) | ||
|
||
db.session.delete(goal) | ||
db.session.commit() | ||
|
||
return jsonify({"details": f"Goal {goal.goal_id} \"{goal.title}\""\ | ||
" successfully deleted"}), 200 | ||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
def assign_tasks(goal_id): | ||
goal = validate_goal(goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
for task_id in request_body["task_ids"]: | ||
task = validate_task(task_id) | ||
|
||
task.goal = goal | ||
|
||
db.session.commit() | ||
|
||
return jsonify({"id": goal.goal_id, | ||
"task_ids": request_body["task_ids"]}), 200 | ||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
def get_all_tasks(goal_id): | ||
goal = validate_goal(goal_id) | ||
|
||
tasks = [] | ||
for task in goal.tasks: | ||
tasks.append(task.to_json()) | ||
|
||
goal_response = goal.to_json() | ||
goal_response["tasks"] = tasks | ||
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. Wonder if you could refactor to_json() so that the logic on line 88 gets moved into the method |
||
|
||
return jsonify(goal_response), 200 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from flask import abort, make_response | ||
from ..models.task import Task | ||
from ..models.goal import Goal | ||
|
||
def validate_task(id): | ||
try: | ||
id = int(id) | ||
except: | ||
abort(make_response({"message": f"{id} is not a valid id"}, 400)) | ||
|
||
task = Task.query.get(id) | ||
|
||
if not task: | ||
abort(make_response({"message": f"Task {id} not found"}, 404)) | ||
|
||
return task | ||
|
||
def validate_goal(id): | ||
try: | ||
id = int(id) | ||
except: | ||
abort(make_response({"message": f"{id} is not a valid id"}, 400)) | ||
|
||
goal = Goal.query.get(id) | ||
|
||
if not goal: | ||
abort(make_response({"message": f"Goal {id} not found"}, 404)) | ||
|
||
return goal |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
from flask import Blueprint, jsonify, make_response, request, abort | ||
from app import db | ||
from app.models.task import Task | ||
from .helpers import validate_task | ||
from datetime import datetime, timezone | ||
import requests | ||
import os | ||
|
||
tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") | ||
|
||
@tasks_bp.route("", methods=["POST"]) | ||
def create_task(): | ||
request_body = request.get_json() | ||
|
||
if request_body.get("title") and request_body.get("description"): | ||
new_task = Task.create(request_body) | ||
else: | ||
abort(make_response({"details": "Invalid data"}, 400)) | ||
|
||
db.session.add(new_task) | ||
db.session.commit() | ||
|
||
return make_response(jsonify({"task": new_task.to_json()}), 201) | ||
|
||
@tasks_bp.route("", methods=["GET"]) | ||
def get_all_tasks(): | ||
sort_query = request.args.get("sort") | ||
|
||
if sort_query: | ||
if sort_query == "asc": | ||
tasks = Task.query.order_by(Task.title) | ||
elif sort_query == "desc": | ||
tasks = Task.query.order_by(Task.title.desc()) | ||
|
||
else: | ||
tasks = Task.query.all() | ||
|
||
task_response_body = [] | ||
for task in tasks: | ||
task_response_body.append(task.to_json()) | ||
|
||
return jsonify(task_response_body), 200 | ||
|
||
@tasks_bp.route("/<task_id>", methods=["GET"]) | ||
def get_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_task(task_id): | ||
task = validate_task(task_id) | ||
|
||
request_body = request.get_json() | ||
|
||
if request_body.get("title") and request_body.get("description"): | ||
task.update(request_body) | ||
else: | ||
abort(make_response({"details": "Invalid data"}, 400)) | ||
|
||
db.session.commit() | ||
|
||
return 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 jsonify({"details": f"Task {task.task_id} \"{task.title}\""\ | ||
" successfully deleted"}), 200 | ||
|
||
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||
def mark_complete(task_id): | ||
task = validate_task(task_id) | ||
already_completed = bool(task.completed_at) | ||
|
||
time = datetime.now(timezone.utc) | ||
|
||
task.completed_at = time | ||
|
||
db.session.commit() | ||
|
||
if not already_completed: | ||
key = os.environ.get("SLACK_API") | ||
payload = { | ||
"channel": "task-notifications", | ||
"text": f"Someone just completed the task {task.title}" | ||
} | ||
header = {"Authorization": f"Bearer {key}"} | ||
requests.post("https://slack.com/api/chat.postMessage", params=payload, | ||
headers=header) | ||
Comment on lines
+86
to
+94
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 in a helper function and calling it here to make mark_complete() a bit more concise. |
||
|
||
return jsonify({"task": task.to_json()}), 200 | ||
|
||
@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||
def mark_incomplete(task_id): | ||
task = validate_task(task_id) | ||
|
||
if task.completed_at: | ||
task.completed_at = None | ||
|
||
db.session.commit() | ||
|
||
return jsonify({"task": task.to_json()}), 200 |
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.
Consider adding nullable=False to ensure every goal requires a title.