-
Notifications
You must be signed in to change notification settings - Fork 127
nadia sarkissian - zoisite #108
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: main
Are you sure you want to change the base?
Changes from all commits
f692c25
0894787
f2df6b3
6e4ff9a
79ec1b7
dfcbd56
92a47a1
8537c01
cf4d0c9
32c4222
f9976f2
7b019b3
6db8193
fc31131
cbdee8e
c3d1629
4544c05
ba51bbf
c2b7b4c
301bf02
111448d
c3b2a6a
7ce979f
bb3932e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 recommend placing this file in the routes directory to keep it close to the code that uses it. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from flask import jsonify, abort, make_response | ||
|
||
def validate_model(cls, model_id): | ||
try: | ||
model_id = int(model_id) | ||
except: | ||
abort(make_response(jsonify({"message":f"{cls.__name__} {model_id} invalid"}), 400)) | ||
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. To keep code shorter and easier to read, I'd suggest splitting this line up. |
||
|
||
model = cls.query.get(model_id) | ||
|
||
if not model: | ||
abort(make_response(jsonify({"message":f"{cls.__name__} {model_id} not found"}), 404)) | ||
|
||
return model |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,22 @@ | ||
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, tasks=False): | ||
goal_as_dict = { | ||
"id": self.goal_id, | ||
"title": self.title, | ||
} | ||
if 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. Nice use of an extra parameter to make the function more flexible! |
||
goal_as_dict["tasks"] = [task.to_dict() for task in self.tasks] | ||
|
||
return goal_as_dict | ||
|
||
@classmethod | ||
def from_dict(cls, goal_data): | ||
return Goal( | ||
title=goal_data["title"], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,32 @@ | |
|
||
|
||
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, nullable=False) | ||
description = db.Column(db.String, nullable=False) | ||
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") | ||
|
||
def to_dict(self): | ||
task_as_dict = { | ||
"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. Great choice to derive the value of |
||
} | ||
|
||
if self.goal_id: | ||
task_as_dict["goal_id"] = self.goal_id | ||
|
||
return task_as_dict | ||
@classmethod | ||
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 PEP8 best practice for white space between functions is to use a single blank line to separate them |
||
def from_dict(cls, task_data): | ||
if "completed_at" not in task_data: | ||
task_data["completed_at"] = None | ||
Comment on lines
+26
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. Nice conditional to ensure you avoid a KeyError if the user didn't send a |
||
|
||
return Task( | ||
title=task_data["title"], | ||
description=task_data["description"], | ||
completed_at=task_data["completed_at"] | ||
) |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
from app import db | ||
from app.models.goal import Goal | ||
from app.models.task import Task | ||
from flask import Blueprint, jsonify, abort, make_response, request | ||
from app.helpers import validate_model | ||
|
||
goals_bp = Blueprint("goals", __name__, url_prefix="/goals") | ||
|
||
@goals_bp.route("", methods=["GET"]) | ||
def get_all_goals(): | ||
goals = Goal.query.all() | ||
results = [goal.to_dict() for goal in goals] | ||
|
||
return jsonify(results) | ||
|
||
@goals_bp.route("/<goal_id>", methods=["GET"]) | ||
def get_one_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
response = {"goal": goal.to_dict()} | ||
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 way to add what you need for the response while getting to reuse the to_dict function! |
||
|
||
return jsonify(response) | ||
|
||
@goals_bp.route("", methods=["POST"]) | ||
def create_goal(): | ||
request_body = request.get_json() | ||
|
||
new_goal_is_valid = "title" in request_body | ||
if not new_goal_is_valid: | ||
abort(make_response(jsonify({"details":"Invalid data"}), 400)) | ||
|
||
new_goal = Goal.from_dict(request_body) | ||
db.session.add(new_goal) | ||
db.session.commit() | ||
|
||
response = {"goal": new_goal.to_dict()} | ||
|
||
return make_response((jsonify(response)), 201) | ||
|
||
@goals_bp.route("<goal_id>", methods=["DELETE"]) | ||
def delete_one_goal(goal_id): | ||
goal_to_delete = validate_model(Goal, goal_id) | ||
|
||
db.session.delete(goal_to_delete) | ||
db.session.commit() | ||
|
||
message = {"details":f"Goal {goal_id} \"{goal_to_delete.title}\" successfully deleted"} | ||
return make_response(message, 200) | ||
|
||
@goals_bp.route("/<goal_id>", methods=["PUT"]) | ||
def update_one_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
updated_data = request.get_json() | ||
|
||
goal.title = updated_data["title"] | ||
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. There's some error handling in |
||
|
||
db.session.commit() | ||
|
||
response = {"goal": goal.to_dict()} | ||
|
||
return make_response(response, 200) | ||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
def create_tasks_for_one_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
request_body = request.get_json() | ||
|
||
for task_id in request_body["task_ids"]: | ||
task = validate_model(Task, task_id) | ||
task.goal_id = goal.goal_id | ||
db.session.commit() | ||
|
||
task_id_list = [task.task_id for task in goal.tasks] | ||
|
||
response_body = { | ||
"id":goal.goal_id, | ||
"task_ids": task_id_list | ||
} | ||
return jsonify(response_body) | ||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
def get_all_tasks_of_one_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
goal = goal.to_dict(tasks=True) | ||
return jsonify(goal) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
from app import db | ||
from app.models.task import Task | ||
from flask import Blueprint, jsonify, abort, make_response, request | ||
from datetime import datetime | ||
from app.helpers import validate_model | ||
import os | ||
import requests | ||
|
||
tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") | ||
|
||
@tasks_bp.route("", methods=["GET"]) | ||
def get_all_tasks(): | ||
|
||
sort_direction = request.args.get("sort") | ||
|
||
if sort_direction == "asc": | ||
tasks = Task.query.order_by(Task.title) | ||
elif sort_direction == "desc": | ||
tasks = Task.query.order_by(Task.title.desc()) | ||
else: | ||
tasks = Task.query.all() | ||
|
||
results = [task.to_dict() for task in tasks] | ||
Comment on lines
+14
to
+23
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 could simplify the if/else tree a little if we get a reference to the task_query = Task.query
sort_direction = request.args.get("sort")
if sort_direction == "asc":
task_query = task_query.order_by(Task.title.asc())
elif sort_direction == "desc":
task_query = task_query.order_by(Task.title.desc())
results = [task.to_dict() for task in task_query.all()] |
||
|
||
return jsonify(results) | ||
|
||
@tasks_bp.route("/<task_id>", methods=["GET"]) | ||
def get_one_task(task_id): | ||
task = validate_model(Task, task_id) | ||
response = {"task": task.to_dict()} | ||
|
||
return jsonify(response) | ||
|
||
@tasks_bp.route("", methods=["POST"]) | ||
def create_task(): | ||
request_body = request.get_json() | ||
|
||
new_task_is_valid = "title" in request_body and "description" in request_body | ||
if not new_task_is_valid: | ||
abort(make_response(jsonify({"details":"Invalid data"}), 400)) | ||
|
||
new_task = Task.from_dict(request_body) | ||
db.session.add(new_task) | ||
db.session.commit() | ||
|
||
response = {"task": new_task.to_dict()} | ||
|
||
return make_response((jsonify(response)), 201) | ||
|
||
@tasks_bp.route("/<task_id>", methods=["PUT"]) | ||
def update_one_task(task_id): | ||
task = validate_model(Task, task_id) | ||
updated_data = request.get_json() | ||
|
||
task.title = updated_data["title"] | ||
task.description = updated_data["description"] | ||
|
||
db.session.commit() | ||
|
||
response = {"task": task.to_dict()} | ||
|
||
return make_response(response, 200) | ||
|
||
@tasks_bp.route("<task_id>", methods=["DELETE"]) | ||
def delete_one_task(task_id): | ||
task_to_delete = validate_model(Task, task_id) | ||
|
||
db.session.delete(task_to_delete) | ||
db.session.commit() | ||
|
||
message = {"details":f"Task {task_id} \"{task_to_delete.title}\" successfully deleted"} | ||
return make_response(message, 200) | ||
|
||
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||
def complete_one_task(task_id): | ||
task = validate_model(Task, task_id) | ||
|
||
if not task.completed_at: | ||
task.completed_at = datetime.now() | ||
|
||
db.session.commit() | ||
|
||
url = "https://slack.com/api/chat.postMessage" | ||
SLACK_API_TOKEN = os.environ.get("SLACK_API_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.
|
||
headers = {"Authorization": f"Bearer {SLACK_API_TOKEN}"} | ||
message = f"Someone just completed the task {task.title}" | ||
data = {"channel": "random", "text": message} | ||
|
||
requests.post(url, headers=headers, data=data) | ||
|
||
response = {"task": task.to_dict()} | ||
return make_response(response, 200) | ||
|
||
@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||
def incomplete_one_task(task_id): | ||
task = validate_model(Task, task_id) | ||
|
||
if task.completed_at: | ||
task.completed_at = None | ||
|
||
db.session.commit() | ||
response = {"task": task.to_dict()} | ||
return make_response(response, 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.
Nice choice to split up the routes into files that are more specific to the resources they work with!