-
Notifications
You must be signed in to change notification settings - Fork 146
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
Marie Keefer Task List API #117
base: master
Are you sure you want to change the base?
Changes from all commits
31641a4
4a7b81d
5addb0b
ce33b6f
5c9161a
ef9a039
44718ab
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,89 @@ | ||
from app import db | ||
from app.models.goal import Goal | ||
from app.models.task import Task | ||
from flask import Blueprint,jsonify, make_response, request | ||
from app.helper_functions import validate_model | ||
from sqlalchemy import asc, desc | ||
|
||
|
||
goals_bp = Blueprint("goals_bp", __name__, url_prefix="/goals") | ||
|
||
@goals_bp.route("", methods=["POST"]) | ||
def create_goal(): | ||
request_body = request.get_json() | ||
if not "title" in request_body: | ||
return make_response({"details": "Invalid data"}, 400) | ||
|
||
new_goal = Goal.from_dict(request_body) | ||
|
||
db.session.add(new_goal) | ||
db.session.commit() | ||
|
||
return make_response({"goal": new_goal.to_dict()}, 201) | ||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
def create_goal_tasks(goal_id): | ||
request_body = request.get_json() | ||
goal = validate_model(Goal, goal_id) | ||
|
||
task_list = [] | ||
for task_id in request_body["task_ids"]: | ||
task = validate_model(Task, task_id) | ||
task.goal = goal | ||
task_list.append(task_id) | ||
|
||
db.session.commit() | ||
|
||
return make_response({"id": goal.goal_id, "task_ids": task_list}, 200) | ||
|
||
@goals_bp.route("", methods=["GET"]) | ||
def read_all_goals(): | ||
sort_query = request.args.get("sort") | ||
if sort_query: | ||
if sort_query == "asc": | ||
goals = Goal.query.order_by(Goal.title.asc()).all() | ||
elif sort_query == "desc": | ||
goals = Goal.query.order_by(Goal.title.desc()).all() | ||
else: | ||
goals = Goal.query.all() | ||
|
||
goal_response = [] | ||
for goal in goals: | ||
goal_response.append(goal.to_dict()) | ||
return make_response(jsonify(goal_response), 200) | ||
|
||
@goals_bp.route("/<goal_id>", methods=["GET"]) | ||
def read_one_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
|
||
return make_response({"goal": goal.to_dict()}, 200) | ||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
def read_tasks_of_one_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
|
||
task_list = [] | ||
for task in goal.tasks: | ||
task_list.append(task.to_dict()) | ||
|
||
return make_response({"id": goal.goal_id, "title": goal.title, "tasks": task_list}) | ||
|
||
@goals_bp.route("/<goal_id>", methods=["PUT"]) | ||
def update_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
request_body = request.get_json() | ||
|
||
goal.title = request_body["title"] | ||
|
||
db.session.commit() | ||
|
||
return make_response({"goal": goal.to_dict()}, 200) | ||
|
||
@goals_bp.route("/<goal_id>", methods=["DELETE"]) | ||
def delete_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
|
||
db.session.delete(goal) | ||
db.session.commit() | ||
|
||
return make_response({"details":f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"}), 200 | ||
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! We could also do this: return make_response({"details":f"Goal {goal.goal_id} '{goal.title}' successfully deleted"}), 200 We can take advantage of the two different sets of quotes |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from app import db | ||
from flask import abort, make_response | ||
import os, requests | ||
|
||
def validate_model(cls, model_id): | ||
try: | ||
model_id = int(model_id) | ||
except: | ||
abort(make_response({"message": f"{cls.__name__} {model_id} invalid"}, 400)) | ||
|
||
model = cls.query.get(model_id) | ||
|
||
if not model: | ||
abort(make_response({"message": f"{cls.__name__} {model_id} not found"}, 404)) | ||
|
||
return model | ||
|
||
|
||
def slack_bot_message(message): | ||
slack_api_key = os.environ.get("SLACK_BOT_TOKEN") | ||
slack_url = "https://slack.com/api/chat.postMessage" | ||
header = {"Authorization": slack_api_key} | ||
|
||
query_params = { | ||
"channel": "task-notifications", | ||
"text": message | ||
} | ||
print(slack_api_key) | ||
requests.post(url=slack_url, data=query_params, headers=header) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,18 @@ | |
|
||
class Goal(db.Model): | ||
goal_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. See the |
||
tasks = db.relationship("Task", back_populates="goal", lazy=True) | ||
|
||
def to_dict(self): | ||
|
||
return { | ||
"id": self.goal_id, | ||
"title": self.title | ||
} | ||
|
||
|
||
@classmethod | ||
def from_dict(cls, goal_data): | ||
new_goal = Goal(title=goal_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. This works! But what if we changed the name of the model? Now this would continue creating new_goal = cls(title=goal_data["title"]) |
||
return new_goal |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,42 @@ | |
|
||
|
||
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) | ||
Comment on lines
+6
to
+7
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. It turns out that The way the project emphasized that |
||
completed_at = db.Column(db.DateTime, default=None) | ||
goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True) | ||
goal = db.relationship("Goal", back_populates="tasks") | ||
|
||
def to_dict(self): | ||
if self.completed_at: | ||
|
||
return { | ||
"id": self.task_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": True | ||
} | ||
if self.goal_id and not self.completed_at: | ||
return { | ||
"id": self.task_id, | ||
"goal_id": self.goal_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": False | ||
} | ||
|
||
else: | ||
return { | ||
"id": self.task_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": False | ||
} | ||
|
||
|
||
@classmethod | ||
def from_dict(cls, task_data): | ||
new_task = Task(title=task_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. new_task = cls(title=task_data["title"], |
||
description=task_data["description"]) | ||
return new_task |
This file was deleted.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,94 @@ | ||||||
from app import db | ||||||
from app.helper_functions import validate_model, slack_bot_message | ||||||
from app.models.task import Task | ||||||
from datetime import datetime | ||||||
from flask import Blueprint, abort, jsonify, make_response, request | ||||||
from sqlalchemy import asc, desc | ||||||
from dotenv import load_dotenv | ||||||
load_dotenv() | ||||||
Comment on lines
+7
to
+8
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 get rid of this since we aren't access the environmental variables in this file!
Suggested change
|
||||||
|
||||||
tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") | ||||||
|
||||||
|
||||||
# -------Routes------- | ||||||
@tasks_bp.route("", methods=["POST"]) | ||||||
def create_task(): | ||||||
request_body = request.get_json() | ||||||
if not "title" in request_body or not "description" in request_body: | ||||||
return make_response({"details": "Invalid data"}, 400) | ||||||
|
||||||
new_task = Task.from_dict(request_body) | ||||||
|
||||||
db.session.add(new_task) | ||||||
db.session.commit() | ||||||
|
||||||
return make_response({"task": new_task.to_dict()}, 201) | ||||||
|
||||||
@tasks_bp.route("", methods=["GET"]) | ||||||
def read_all_tasks(): | ||||||
sort_query = request.args.get("sort") | ||||||
if sort_query: | ||||||
if sort_query == "asc": | ||||||
tasks = Task.query.order_by(Task.title.asc()).all() | ||||||
elif sort_query == "desc": | ||||||
tasks = Task.query.order_by(Task.title.desc()).all() | ||||||
else: | ||||||
tasks = Task.query.all() | ||||||
|
||||||
task_response = [] | ||||||
for task in tasks: | ||||||
task_response.append(task.to_dict()) | ||||||
return make_response(jsonify(task_response), 200) | ||||||
|
||||||
|
||||||
@tasks_bp.route("/<task_id>", methods=["GET"]) | ||||||
def read_one_task(task_id): | ||||||
task = validate_model(Task, task_id) | ||||||
|
||||||
return make_response({"task": task.to_dict()}, 200) | ||||||
|
||||||
@tasks_bp.route("/<task_id>", methods=["PUT"]) | ||||||
def update_task(task_id): | ||||||
task = validate_model(Task, task_id) | ||||||
request_body = request.get_json() | ||||||
|
||||||
task.title = request_body["title"] | ||||||
task.description = request_body["description"] | ||||||
Comment on lines
+55
to
+56
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 turn this into a helper method in |
||||||
|
||||||
db.session.commit() | ||||||
|
||||||
return make_response({"task": task.to_dict()}, 200) | ||||||
|
||||||
@tasks_bp.route("/<task_id>", methods=["DELETE"]) | ||||||
def delete_task(task_id): | ||||||
task = validate_model(Task, task_id) | ||||||
|
||||||
db.session.delete(task) | ||||||
db.session.commit() | ||||||
|
||||||
return make_response({"details":f"Task {task.task_id} \"{task.title}\" successfully deleted"}), 200 | ||||||
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! We could also do this: return make_response({"details":f"Task {task.task_id} '{task.title}' successfully deleted"}), 200 We can take advantage of the two different sets of quotes |
||||||
|
||||||
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||||||
def mark_task_complete(task_id): | ||||||
task = validate_model(Task, task_id) | ||||||
|
||||||
task.completed_at = datetime.utcnow() | ||||||
|
||||||
db.session.commit() | ||||||
|
||||||
slack_bot_message(f"Someone just completed the task {task.title}") | ||||||
|
||||||
return make_response({"task": task.to_dict()}, 200) | ||||||
|
||||||
@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||||||
def mark_task_incomplete(task_id): | ||||||
task = validate_model(Task, task_id) | ||||||
|
||||||
task.completed_at = None | ||||||
db.session.commit() | ||||||
|
||||||
return make_response({"task": task.to_dict()}, 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.
whoops! forgot your status code!