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

Melinda H. Pine #86

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
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()'
11 changes: 11 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ def create_app(test_config=None):
app = Flask(__name__)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

# import models here




if test_config is None:
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_DATABASE_URI")
Expand All @@ -30,5 +35,11 @@ def create_app(test_config=None):
migrate.init_app(app, db)

# Register Blueprints here

from .routes import tasks_bp
app.register_blueprint(tasks_bp)

from .routes import goals_bp
app.register_blueprint(goals_bp)

return app
34 changes: 33 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
from flask import current_app
from sqlalchemy.orm import backref
from app import db


class Goal(db.Model):
goal_id = db.Column(db.Integer, primary_key=True)
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
# genres = db.relationship("Genre", secondary="books_genres", backref="books")
tasks = db.relationship("Task", backref="goal", lazy=True)

def to_dict(self):
if self.list_of_task_ids():

Choose a reason for hiding this comment

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

Storing this result of function call here in a variable (ie task_ids = self.list_of_task_ids()) and then testing could create a slight optimization, because then the variable could be used on line 19 ("task_ids": task_ids) instead of calling the function again.


return{

"id": self.id,
"title": self.title,
"tasks_ids": self.list_of_task_ids()
}
else:
return{
"id": self.id,
"title": self.title
}

def list_of_task_ids(self):
task_ids = [task.id for task in self.tasks]
return task_ids

def task_list(self):
list = []
for task in self.tasks:
list.append(task.to_dict)
Comment on lines +32 to +34

Choose a reason for hiding this comment

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

I think there's a small bug here:

Suggested change
list = []
for task in self.tasks:
list.append(task.to_dict)
list = []
for task in self.tasks:
list.append(task.to_dict())

This loop is also a great candidate for a list comprehension similar to line 28:

Suggested change
list = []
for task in self.tasks:
list.append(task.to_dict)
list = [task.to_dict() for task in self.tasks]


return list


36 changes: 35 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,40 @@
from flask import current_app
from app import db

# task_id: a primary key for each task
# title: text to name the task
# description: text to describe the task
# completed_at: a datetime that has the date that a task is completed on. Can be nullable, and contain a null value. A task with a null value for completed_at has not been completed.

class Task(db.Model):
task_id = db.Column(db.Integer, primary_key=True)
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
description = db.Column(db.String)
completed_at = db.Column(db.DateTime,nullable=True)
goal_id = db.Column(db.Integer, db.ForeignKey('goal.id'), nullable=True)

def to_dict(self):
if self.goal_id is None:
return {
"id": self.id,
"title": self.title,
"description": self.description,
"is_complete": self.check_for_complete_task(),
}
else:

return{

"id": self.id,
"title": self.title,
"description": self.description,
"is_complete": self.check_for_complete_task(),
"goal_id": self.goal_id

Choose a reason for hiding this comment

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

👍


}

def check_for_complete_task(self):

Choose a reason for hiding this comment

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

👍

if self.completed_at:
return True
return False

272 changes: 271 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,272 @@
from flask import Blueprint
from flask.wrappers import Response
from app.models.task import Task
from app import db
from flask import Blueprint, jsonify, make_response, request, abort
from datetime import date
import os
import requests
from dotenv import load_dotenv
from app.models.goal import Goal

# handle_tasks handles GET and POST requests for the /tasks endpoint


tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks")
def valid_int(number,parameter_type):

Choose a reason for hiding this comment

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

Great helper!

try:
int(number)
except:
abort(make_response({"error":f"{parameter_type} must be an int"},400))

@tasks_bp.route("", methods=["GET", "POST"])
def handle_tasks():
# Wave 1: Get Tasks: Getting Saved Tasks
if request.method == "GET":
sort = request.args.get("sort")
if sort == "asc":
tasks = Task.query.order_by(Task.title)
elif sort == "desc":
tasks = Task.query.order_by(Task.title.desc())
else:
tasks = Task.query.all()
#Wave 1: Get Tasks: No Saved Tasks
tasks_response = []
for task in tasks:
has_complete = task.completed_at
tasks_response.append(
{
"description": task.description,
"id": task.id,
"is_complete": False if has_complete == None else has_complete,
"title": task.title,
}
)
Comment on lines +36 to +43

Choose a reason for hiding this comment

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

This works, I recommend using the to_dict helper function to DRY this section:

Suggested change
tasks_response.append(
{
"description": task.description,
"id": task.id,
"is_complete": False if has_complete == None else has_complete,
"title": task.title,
}
)
tasks_response.append(task.to_dict())

return jsonify(tasks_response)
# Wave 1: Create a Task: Valid Task With null completed_at
elif request.method == "POST":
request_body = request.get_json()
#Wave 1: Create A Task: Missing Title
if "title" not in request_body or "description" not in request_body or "completed_at" not in request_body:
return jsonify ({
"details": "Invalid data"
}), 400


new_task = Task(
title=request_body["title"],
description=request_body["description"],
completed_at=request_body["completed_at"]
)
db.session.add(new_task)
db.session.commit()

#Wave 1: Create A Task: Valid Task with null completed_at 201 CREATED


return jsonify({"task":new_task.to_dict()}),201
# handle_one_task handles GET,PUT and DELETE requests for the tasks/task_id endpoint
@tasks_bp.route("/<task_id>", methods=["GET", "PUT", "DELETE"])
def handle_one_task(task_id):
valid_int(task_id,"task_id")
task = Task.query.get_or_404(task_id)

Choose a reason for hiding this comment

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

Great use of get_or_404!

# Wave 1: Get One Task: One Saved Task
if request.method == "GET":
has_complete = task.completed_at
if task.goal_id:
task_response={
"task": {
"id": task.id,
"goal_id": task.goal_id,
"title": task.title,
"description": task.description,
"is_complete": False if has_complete == None else has_complete,

}
}
else:
task_response={
"task": {
"id": task.id,
"title": task.title,
"description": task.description,
"is_complete": False if has_complete == None else has_complete,

}
}
Comment on lines +75 to +95

Choose a reason for hiding this comment

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

Task to_dict handles the existence of goal_id.

Suggested change
if task.goal_id:
task_response={
"task": {
"id": task.id,
"goal_id": task.goal_id,
"title": task.title,
"description": task.description,
"is_complete": False if has_complete == None else has_complete,
}
}
else:
task_response={
"task": {
"id": task.id,
"title": task.title,
"description": task.description,
"is_complete": False if has_complete == None else has_complete,
}
}
task_response = {"task":task.to_dict()}


return jsonify(task_response)
#Wave 1: Update Task, #Wave 1 Update Task: No Matching Task, Update Task 200 OK
elif request.method == "PUT":
form_data = request.get_json()

task.title = form_data["title"]
task.description = form_data["description"]


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

#Wave 1 Delete Task: Deleting A Task, #Wave 1: Delete Task: No Matching Task
elif request.method == "DELETE":
db.session.delete(task)
db.session.commit()
response = {
"details": f'Task {task.id} "{task.title}" successfully deleted'
}
json_response = jsonify(response)
return make_response(json_response, 200)

def slack_bot(title):

Choose a reason for hiding this comment

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

Great helper!

query_path = {
"channel": "melinda-bot",
"text": f"Someone completed the task {title}"
}
header = {
"Authorization": f"Bearer {os.environ.get('BOT')}"
}
response = requests.post("https://slack.com/api/chat.postMessage",params = query_path, headers = header)
return response.json()


#Wave 3
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def handle_completed_task(task_id):

Choose a reason for hiding this comment

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

Great work, the use of helper functions and get_or_404 in this function really streamline the code!

valid_int(task_id,"task_id")
task = Task.query.get_or_404(task_id)
task.completed_at = date.today()
db.session.commit()
slack_bot(task.title)
return jsonify ({"task":task.to_dict()}),200


@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def handle_incompleted_task(task_id):
valid_int(task_id,"task_id")
task = Task.query.get_or_404(task_id)
task.completed_at = None
db.session.commit()
return jsonify ({"task":task.to_dict()}),200


# Wave 5 Creating a Goal Model Blueprint
goals_bp = Blueprint("goals_bp", __name__, url_prefix="/goals")

# Wave 5 Create A Goal: Valid Goal
@goals_bp.route("", methods=["POST"])
def handle_post_goals():

request_body = request.get_json()

if "title" not in request_body:
return jsonify ( {
"details": "Invalid data"
}), 400

new_goal = Goal(
title = request_body["title"]
)
db.session.add(new_goal)
db.session.commit()


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

# Wave 5 Get Goals: Getting Saved Goals
@goals_bp.route("", methods=["GET"])
def handle_goals():
goals = Goal.query.all()
goals_response = []
for goal in goals:
goals_response.append(goal.to_dict())
return jsonify(goals_response), 200


# Wave 5 Update Goal: Update Goal/No Matching Goal
@goals_bp.route("/<goal_id>", methods=["PUT", "GET"])
def handle_update_one_goal(goal_id):
goal = Goal.query.get_or_404(goal_id)
if request.method == "GET":
return jsonify({"goal":goal.to_dict()}),200
elif request.method == "PUT":
form_data = request.get_json()
if "title" not in form_data:
return jsonify( {
"details": "title required"
}), 400

goal.title = form_data["title"]

db.session.commit()
return jsonify({"goal":{"goal_id":goal.id, "title":goal.title}}),200

Choose a reason for hiding this comment

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

Suggested change
return jsonify({"goal":{"goal_id":goal.id, "title":goal.title}}),200
return jsonify({"goal":goal.to_dict()}),200


# Wave 5 Deleting A Goal: Deleting A Goal/No Matching Goal
@goals_bp.route("/<goal_id>", methods=["DELETE"])
def handle_delete_one_goal(goal_id):
goal = Goal.query.get_or_404(goal_id)
db.session.delete(goal)
db.session.commit()
return jsonify( {
"details": f"Goal {goal_id} \"{goal.title}\" successfully deleted"
}),200
# json_response = jsonify(response)
# return make_response(json_response), 200

#Wave # 6
@goals_bp.route("/<goal_id>/tasks", methods=["POST"])
def post_task_ids_to_goal(goal_id):

Choose a reason for hiding this comment

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

👍

valid_int(goal_id,"goal_id")
request_body = request.get_json()
goal = Goal.query.get_or_404(goal_id)
task_ids = request_body["task_ids"]
for task_id in task_ids:
task = Task.query.get(task_id)
goal.tasks.append(task)
db.session.commit()
return jsonify({"id":goal.id, "task_ids": [task.id for task in goal.tasks]}),200

@goals_bp.route("/<goal_id>/tasks", methods=["GET"])
def get_tasks_for_goal(goal_id):
valid_int(goal_id,"goal_id")
goal = Goal.query.get_or_404(goal_id)

tasks = goal.tasks
tasks_list = []
for task in tasks:
tasks_list.append(task.to_dict())
response_body = {"id":goal.id,
"title":goal.title,
"tasks": tasks_list
}


return jsonify(response_body),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