-
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
Whales - Gaby Webb #110
base: master
Are you sure you want to change the base?
Whales - Gaby Webb #110
Changes from all commits
93663e3
d352f46
0d566d4
38d9f8a
9b673c2
b7af28f
e3edf61
5a6a532
55d99e4
4f8eb46
02d7df4
f088c2a
4a52e54
9dbf2f7
9228f5c
fe953dd
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 | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,4 +2,14 @@ | |||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
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', backref='goal', lazy=True) | ||||||||||||||||||||
|
||||||||||||||||||||
def create_goal_dict(self): | ||||||||||||||||||||
goal_dict = { | ||||||||||||||||||||
"id": self.goal_id, | ||||||||||||||||||||
"title": self.title, | ||||||||||||||||||||
} | ||||||||||||||||||||
return goal_dict | ||||||||||||||||||||
Comment on lines
+10
to
+14
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. Since you immediately return this you don't need to assign it to a variable:
Suggested change
|
||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,5 +1,25 @@ | ||||||||||||||||||||||||||||||||
from app import db | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
class Task(db.Model): | ||||||||||||||||||||||||||||||||
task_id = db.Column(db.Integer, primary_key=True) | ||||||||||||||||||||||||||||||||
task_id = db.Column(db.Integer, primary_key=True, autoincrement=True) | ||||||||||||||||||||||||||||||||
goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id')) | ||||||||||||||||||||||||||||||||
title = db.Column(db.String) | ||||||||||||||||||||||||||||||||
description = db.Column(db.String) | ||||||||||||||||||||||||||||||||
completed_at = db.Column(db.DateTime) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
def create_task_dict(self): | ||||||||||||||||||||||||||||||||
task_dict = { | ||||||||||||||||||||||||||||||||
"id": self.task_id, | ||||||||||||||||||||||||||||||||
"title": self.title, | ||||||||||||||||||||||||||||||||
"description": self.description | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
if self.completed_at is None: | ||||||||||||||||||||||||||||||||
task_dict["is_complete"] = False | ||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||
task_dict["is_complete"] = True | ||||||||||||||||||||||||||||||||
Comment on lines
+11
to
+19
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 can be simplified to:
Suggested change
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
if self.goal_id: | ||||||||||||||||||||||||||||||||
task_dict["goal_id"] = self.goal_id | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
return task_dict | ||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1 +1,246 @@ | ||||||
from flask import Blueprint | ||||||
from app import db | ||||||
from flask import Blueprint, jsonify, make_response, request, abort | ||||||
from app.models.task import Task | ||||||
from app.models.goal import Goal | ||||||
from datetime import datetime | ||||||
from dotenv import load_dotenv | ||||||
import os, requests | ||||||
|
||||||
tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") | ||||||
|
||||||
@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 {"details": "Invalid data"}, 400 | ||||||
|
||||||
if "completed_at" in request_body: | ||||||
new_task = Task(title=request_body["title"], | ||||||
description=request_body["description"], | ||||||
completed_at=request_body["completed_at"]) | ||||||
else: | ||||||
new_task = Task(title=request_body["title"], | ||||||
description=request_body["description"]) | ||||||
|
||||||
db.session.add(new_task) | ||||||
db.session.commit() | ||||||
|
||||||
response = { | ||||||
"task": new_task.create_task_dict() | ||||||
} | ||||||
return response, 201 | ||||||
|
||||||
@tasks_bp.route("", methods=["GET"]) | ||||||
def get_all_tasks(): | ||||||
title_sort_query = request.args.get("sort") | ||||||
if title_sort_query == "asc": | ||||||
tasks = Task.query.order_by(Task.title.asc()) | ||||||
elif title_sort_query == "desc": | ||||||
tasks = Task.query.order_by(Task.title.desc()) | ||||||
else: | ||||||
tasks = Task.query.all() | ||||||
|
||||||
response = [] | ||||||
for task in tasks: | ||||||
response.append(task.create_task_dict()) | ||||||
|
||||||
return jsonify(response) | ||||||
|
||||||
@tasks_bp.route("/<task_id>", methods=["GET"]) | ||||||
def get_one_task(task_id): | ||||||
task = get_one_task_or_abort(task_id) | ||||||
response = { | ||||||
"task": task.create_task_dict() | ||||||
} | ||||||
return response | ||||||
|
||||||
def get_one_task_or_abort(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. I like this helper function. It's really clearly named and clean. 😃 (You even have great error messages!) |
||||||
try: | ||||||
task_id = int(task_id) | ||||||
except ValueError: | ||||||
response = {"msg":f"Invalid task id: {task_id}. ID must be an integer."} | ||||||
abort(make_response(jsonify(response), 400)) | ||||||
|
||||||
requested_task = Task.query.get(task_id) | ||||||
if requested_task is None: | ||||||
response = {"msg":f"Could not find task with id: {task_id}"} | ||||||
abort(make_response(jsonify(response), 404)) | ||||||
|
||||||
return requested_task | ||||||
|
||||||
@tasks_bp.route("/<task_id>", methods=["PUT"]) | ||||||
def replace_task(task_id): | ||||||
task = get_one_task_or_abort(task_id) | ||||||
request_body = request.get_json() | ||||||
|
||||||
task.title = request_body["title"] | ||||||
task.description = request_body["description"] | ||||||
|
||||||
db.session.commit() | ||||||
response = { | ||||||
"task": task.create_task_dict() | ||||||
} | ||||||
return response | ||||||
|
||||||
@tasks_bp.route("/<task_id>", methods=["DELETE"]) | ||||||
def delete_task(task_id): | ||||||
task = get_one_task_or_abort(task_id) | ||||||
db.session.delete(task) | ||||||
db.session.commit() | ||||||
response = {"details": f'Task {task.task_id} "{task.title}" successfully deleted'} | ||||||
return response | ||||||
|
||||||
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||||||
def mark_task_complete(task_id): | ||||||
task = get_one_task_or_abort(task_id) | ||||||
task.completed_at = datetime.now() | ||||||
|
||||||
db.session.commit() | ||||||
response = { | ||||||
"task": task.create_task_dict() | ||||||
} | ||||||
|
||||||
path = "https://slack.com/api/chat.postMessage" | ||||||
data = { | ||||||
"channel": "task-notifications", | ||||||
"text": f"Someone just completed the task {task.title}" | ||||||
} | ||||||
headers = { | ||||||
"authorization": "Bearer " + os.environ.get("SLACKBOT_API_KEY") | ||||||
} | ||||||
|
||||||
post_message = requests.post(path, data=data, headers=headers) | ||||||
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. Style: Since you don't do anything with this variable it's a best practice not to store it:
Suggested change
|
||||||
|
||||||
return response | ||||||
|
||||||
@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||||||
def mark_task_incomplete(task_id): | ||||||
task = get_one_task_or_abort(task_id) | ||||||
task.completed_at = None | ||||||
db.session.commit() | ||||||
response = { | ||||||
"task": task.create_task_dict() | ||||||
} | ||||||
return response | ||||||
|
||||||
goals_bp = Blueprint("goals", __name__, url_prefix="/goals") | ||||||
|
||||||
@goals_bp.route("", methods=["POST"]) | ||||||
def create_goal(): | ||||||
request_body = request.get_json() | ||||||
|
||||||
if "title" not in request_body: | ||||||
return {"details": "Invalid data"}, 400 | ||||||
|
||||||
new_goal = Goal(title=request_body["title"]) | ||||||
|
||||||
db.session.add(new_goal) | ||||||
db.session.commit() | ||||||
|
||||||
response = { | ||||||
"goal": new_goal.create_goal_dict() | ||||||
} | ||||||
return response, 201 | ||||||
|
||||||
@goals_bp.route("", methods=["GET"]) | ||||||
def get_all_goals(): | ||||||
goals = Goal.query.all() | ||||||
response = [] | ||||||
for goal in goals: | ||||||
response.append( | ||||||
goal.create_goal_dict() | ||||||
) | ||||||
|
||||||
return jsonify(response) | ||||||
|
||||||
@goals_bp.route("/<goal_id>", methods=["GET"]) | ||||||
def get_one_goal(goal_id): | ||||||
goal = get_one_goal_or_abort(goal_id) | ||||||
response = { | ||||||
"goal": goal.create_goal_dict() | ||||||
} | ||||||
return response | ||||||
|
||||||
def get_one_goal_or_abort(goal_id): | ||||||
try: | ||||||
goal_id = int(goal_id) | ||||||
except ValueError: | ||||||
response = {"msg":f"Invalid goal id: {goal_id}. ID must be an integer."} | ||||||
abort(make_response(jsonify(response), 400)) | ||||||
|
||||||
requested_goal = Goal.query.get(goal_id) | ||||||
if not requested_goal: | ||||||
response = {"msg":f"Could not find goal with id: {goal_id}"} | ||||||
abort(make_response(jsonify(response), 404)) | ||||||
|
||||||
return requested_goal | ||||||
|
||||||
@goals_bp.route("/<goal_id>", methods=["PUT"]) | ||||||
def replace_goal(goal_id): | ||||||
goal = get_one_goal_or_abort(goal_id) | ||||||
request_body = request.get_json() | ||||||
|
||||||
goal.title = request_body["title"] | ||||||
db.session.commit() | ||||||
response = { | ||||||
"goal": goal.create_goal_dict() | ||||||
} | ||||||
return response | ||||||
|
||||||
@goals_bp.route("/<goal_id>", methods=["DELETE"]) | ||||||
def delete_goal(goal_id): | ||||||
goal = get_one_goal_or_abort(goal_id) | ||||||
|
||||||
db.session.delete(goal) | ||||||
db.session.commit() | ||||||
response = {"details": f'Goal {goal.goal_id} "{goal.title}" successfully deleted'} | ||||||
|
||||||
return response | ||||||
|
||||||
@goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||||||
def associate_tasks_goals(goal_id): | ||||||
goal = get_one_goal_or_abort(goal_id) | ||||||
request_body = request.get_json() | ||||||
|
||||||
try: | ||||||
task_ids = request_body["task_ids"] | ||||||
except KeyError: | ||||||
return {"msg": "Missing task_ids in request body"}, 400 | ||||||
|
||||||
if not isinstance(task_ids, list): | ||||||
return {"msg": "Expected list of task ids"}, 400 | ||||||
|
||||||
tasks = [] | ||||||
for id in task_ids: | ||||||
tasks.append(get_one_task_or_abort(id)) | ||||||
|
||||||
for task in tasks: | ||||||
task.goal = goal | ||||||
|
||||||
db.session.commit() | ||||||
|
||||||
return { | ||||||
"id": goal.goal_id, | ||||||
"task_ids": task_ids | ||||||
}, 200 | ||||||
|
||||||
@goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||||||
def get_tasks_assoc_goal(goal_id): | ||||||
goal = get_one_goal_or_abort(goal_id) | ||||||
request_body = request.get_json() | ||||||
|
||||||
goal_tasks = [] | ||||||
for task in goal.tasks: | ||||||
goal_tasks.append(task) | ||||||
|
||||||
response = { | ||||||
"id": goal.goal_id, | ||||||
"title": goal.title, | ||||||
"tasks": [] | ||||||
} | ||||||
|
||||||
for task in goal.tasks: | ||||||
response["tasks"].append(task.create_task_dict()) | ||||||
|
||||||
return response |
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.
Style: Since this is a method on the
Goal
class havinggoal
in the name is redundant: