Skip to content
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

Lions - Task List - Nina-Tuyen Tran #132

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: gunicorn 'app:create_app()'
5 changes: 5 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@ def create_app(test_config=None):
migrate.init_app(app, db)

# Register Blueprints here
from app.task_routes import task_bp
app.register_blueprint(task_bp)

from app.goal_routes import goal_bp
app.register_blueprint(goal_bp)

return app
114 changes: 114 additions & 0 deletions app/goal_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
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
import requests
from app.helper_routes import get_one_obj_or_abort

goal_bp = Blueprint("goal_bp", __name__, url_prefix="/goals")
#--------------------------------POST-------------------------------
@goal_bp.route("", methods=["POST"])
def create_goal():
request_body = request.get_json()

if "title" not in request_body:
return jsonify({"details": "Invalid data"}), 400
Comment on lines +14 to +15

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice that you're doing input validation here, but it would be good to let the user know what data was invalid.


new_goal = Goal(title = request_body["title"])


db.session.add(new_goal)
db.session.commit()


return jsonify({"goal": new_goal.to_dict()}), 201

@goal_bp.route("/<goal_id>/tasks", methods=["POST"])
def send_a_task_to_goal(goal_id):
request_body = request.get_json()

chosen_goal = get_one_obj_or_abort(Goal, goal_id)
list_of_chosen_tasks = [get_one_obj_or_abort(Task, task_id) for task_id in request_body["task_ids"]]
# for task_id in request_body["task_ids"]:
# chosen_task = get_one_obj_or_abort(Task, task_id)
# list_of_chosen_tasks.append(chosen_task)

chosen_goal.tasks = list_of_chosen_tasks

db.session.commit()

return jsonify({"id": int(goal_id), "task_ids": request_body["task_ids"]}), 200


#--------------------------------GET-------------------------------
@goal_bp.route("", methods=["GET"])
def get_all_saved_goals():
goals = Goal.query.all()

response = []
for goal in goals:
goal_dict = {
"id": goal.goal_id,
"title": goal.title,
}
response.append(goal_dict)

return jsonify(response), 200

@goal_bp.route("/<goal_id>", methods=["GET"])
def get_one_saved_goal(goal_id):
chosen_goal = get_one_obj_or_abort(Goal, goal_id)
goal = {"id": chosen_goal.goal_id, "title": chosen_goal.title}
return jsonify({"goal": goal}), 200


@goal_bp.route("/<goal_id>/tasks", methods=["GET"])
def get_tasks_of_one_goal(goal_id):
chosen_goal = get_one_obj_or_abort(Goal, goal_id)
#tasks = Task.query.filter_by(goal_id=goal_id)
list_of_tasks = [task.to_dict() for task in chosen_goal.tasks]
#list_of_tasks = [task.to_dict() for task in tasks]
# list_of_tasks = []
# for task in tasks:
# task_dict = {
# "id": task.id,
# "title": task.title,
# "description": task.description,
# "goal_id": int(goal_id),
# "is_complete": task.is_complete

# }
# list_of_tasks.append(task_dict)

chosen_goal_dict = {
"id":chosen_goal.goal_id,
"title":chosen_goal.title,
"tasks": list_of_tasks
}
Comment on lines +83 to +87

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turning a goal into a dict would be a great helper method in the model like the to_dict function you already have.


return jsonify(chosen_goal_dict), 200

#--------------------------------PUT-------------------------------
@goal_bp.route("/<goal_id>", methods=["PUT"])
def update_goal(goal_id):

chosen_goal = get_one_obj_or_abort(Goal, goal_id)
request_body = request.get_json()

chosen_goal.title = request_body["title"]
db.session.commit()

return jsonify({"goal": chosen_goal.to_dict()}), 200

#---------------------------------------DELETE------------------------------------------------
@goal_bp.route("/<goal_id>", methods=["DELETE"])
def delete_one_task(goal_id):
chosen_goal = get_one_obj_or_abort(Goal, goal_id)

db.session.delete(chosen_goal)
db.session.commit()

return jsonify({"details": f'Goal {goal_id} "{chosen_goal.title}" successfully deleted'}), 200



16 changes: 16 additions & 0 deletions app/helper_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from flask import jsonify, abort, make_response

def get_one_obj_or_abort(cls, obj_id):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great helper function

try:
obj_id = int(obj_id)
except ValueError:
response_str = f"Invalid ID. Please submit an integer for the ID."
abort(make_response(jsonify({"Message":response_str}), 400))

matching_obj = cls.query.get(obj_id)

if not matching_obj:
response_str = f"{cls.__name__} with id #{obj_id} was not found in the database."
abort(make_response(jsonify({"Message": response_str}), 404))

return matching_obj
13 changes: 12 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,15 @@


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_dict(self):
tasks_list = [task.to_dict() for task in self.tasks]
goal_dict = {
"id": self.goal_id,
"title": self.title,
}

return goal_dict
33 changes: 31 additions & 2 deletions app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
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)
title = db.Column(db.String)
description = db.Column(db.String)
completed_at = db.Column(db.DateTime, nullable=True)
is_complete = db.Column(db.Boolean, nullable=True)
Comment on lines +7 to +8

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need two fields for this, one would do and it makes things more complicated to keep them in sync. That said it doesn't hurt much.

goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True)
goal = db.relationship("Goal", back_populates = "tasks")


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another helpful method might be a class method which takes a dictionary as an argument and creates an instance of the model.

def to_dict(self):
task_dict = {
"id": self.task_id,
"goal_id": self.goal_id,
"title": self.title,
"description": self.description,
"is_complete": self.is_complete
}

if self.is_complete is None:
task_dict["is_complete"] = False
else:
task_dict["is_complete"] = True

if self.goal_id is None:
task_dict.pop("goal_id")

return task_dict




1 change: 0 additions & 1 deletion app/routes.py

This file was deleted.

190 changes: 190 additions & 0 deletions app/task_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
from app import db
from app.models.task import Task
from flask import Blueprint, jsonify, abort, make_response, request
import requests
from datetime import datetime
import os


task_bp = Blueprint("task_bp", __name__, url_prefix="/tasks")

#---------------------------------------GET------------------------------------------------

def get_one_task_or_abort(task_id):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great helper function

try:
task_id = int(task_id)
except ValueError:
response_str = f"Invalid task ID: {task_id}. ID must be an integer."
abort(make_response(jsonify({"Message":response_str}), 400))

matching_task = Task.query.get(task_id)

if not matching_task:
response_str = f"Task with id #{task_id} was not found in the database."
abort(make_response(jsonify({"Message": response_str}), 404))

return matching_task

@task_bp.route("", methods=["GET"])
def read_all_tasks():

sort_param = request.args.get("sort")
if sort_param == "asc":
tasks = Task.query.order_by(Task.title.asc()).all()
elif sort_param is None:
tasks = Task.query.all()
elif sort_param == "desc":
tasks = Task.query.order_by(Task.title.desc()).all()

response = []
for task in tasks:
task_dict = {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": False
}
response.append(task_dict)
return jsonify(response), 200


name_param = request.args.get("title")

if name_param is None:
tasks = Task.query.all()
else:
tasks = Task.query.filter_by(title=name_param)

response = []

for task in tasks:
if task.completed_at is None:
task_dict = {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": False
}
response.append(task_dict)
else:
task_dict = {
"id": task.task_id,
"title": task.title,
"description": task.description,
"completed at": task.completed_at,
"is_complete": True}
Comment on lines +62 to +75

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be compressed a bit you could just make a local variable set to be equal to task.completed_at == None

response.append(task_dict)
return jsonify(response), 200

@task_bp.route("/<task_id>", methods=["GET"])
def get_one_task(task_id):
chosen_task = get_one_task_or_abort(task_id)

# if chosen_task.completed_at is None:
# task = {
# "id": chosen_task.task_id,
# "title": chosen_task.title,
# "description": chosen_task.description,
# "is_complete": False
# }

# else:
# task = {
# "id": chosen_task.task_id,
# "title": chosen_task.title,
# "description": chosen_task.description,
# "completed at": chosen_task.completed_at,
# "is_complete": True}
Comment on lines +82 to +97

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# if chosen_task.completed_at is None:
# task = {
# "id": chosen_task.task_id,
# "title": chosen_task.title,
# "description": chosen_task.description,
# "is_complete": False
# }
# else:
# task = {
# "id": chosen_task.task_id,
# "title": chosen_task.title,
# "description": chosen_task.description,
# "completed at": chosen_task.completed_at,
# "is_complete": True}


return jsonify({"task": chosen_task.to_dict()}), 200




#-------------------------------------POST------------------------------------------------
@task_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 jsonify({"details": "Invalid data"}), 400
Comment on lines +109 to +111

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good that you're doing data validation here, but I suggest telling the end user what is invalid about the data.


new_task = Task(title = request_body["title"], description = request_body["description"])


db.session.add(new_task)
db.session.commit()


return jsonify({"task": new_task.to_dict()}), 201

#---------------------------------------UPDATE------------------------------------------------
@task_bp.route("/<task_id>", methods=["PUT"])
def update_task(task_id):
chosen_task = get_one_task_or_abort(task_id)

request_body = request.get_json()

if "title" not in request_body or \
"description" not in request_body:
return jsonify({"Message":"Request must include title and description"})

chosen_task.title = request_body["title"]
chosen_task.description = request_body["description"]

db.session.commit()

return jsonify({"task": chosen_task.to_dict()}), 200

#---------------------------------------DELETE------------------------------------------------
@task_bp.route("/<task_id>", methods=["DELETE"])
def delete_one_task(task_id):
chosen_task = get_one_task_or_abort(task_id)

db.session.delete(chosen_task)
db.session.commit()
return jsonify({"details": f'Task {task_id} "{chosen_task.title}" successfully deleted'}), 200


#---------------------------------------PATCH------------------------------------------------
@task_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def mark_complete_on_incompleted_task(task_id):
chosen_task = get_one_task_or_abort(task_id)
today = datetime.now()

if chosen_task.completed_at is None:
chosen_task.completed_at = today
chosen_task.is_complete = True

else:
chosen_task.completed_at = today
chosen_task.is_complete = True
db.session.commit()

header = os.environ.get("api_slack")
url = "http://slack.com/api/chat.postMessage"
response_str = f"Someone just completed the task {chosen_task.title}"
data = {"channel":"task-notifications", "text": response_str}
r = requests.post(url, params=data, headers={"Authorization":header})
Comment on lines +165 to +169

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interacting with Slack would make a good candidate for a helper function.



return jsonify({"task": chosen_task.to_dict()}), 200



@task_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def mark_incomplete_on_completed_task(task_id):
chosen_task = get_one_task_or_abort(task_id)
today = datetime.now()

if chosen_task.completed_at:
chosen_task.completed_at = None
chosen_task.is_complete = None
else:
chosen_task.completed_at = None
chosen_task.is_complete = None

db.session.commit()
return jsonify({"task": chosen_task.to_dict()}), 200

1 change: 1 addition & 0 deletions migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generic single-database configuration.
Loading