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

Zoisite - Jasmin W. #114

Open
wants to merge 16 commits into
base: main
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
5 changes: 4 additions & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,8 @@ def create_app(test_config=None):
migrate.init_app(app, db)

# Register Blueprints here

from .routes import tasks_bp, goals_bp
app.register_blueprint(tasks_bp)
app.register_blueprint(goals_bp)
return app

28 changes: 28 additions & 0 deletions app/helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from flask import abort, make_response
from app.models.task import Task
from app.models.goal import Goal


def validate_id(id):
try:
id = int(id)
except:
abort(make_response({"message":f"this is not a valid id: {id}"}, 400))

task = Task.query.get(id)
if not task:
abort(make_response({"message":f"id {id} not found!"}, 404))
return task

def validate_goal(id):
try:
id = int(id)
except:
abort(make_response({"message":f"this is not a valid id: {id}"}, 400))

goal = Goal.query.get(id)
if not goal:
abort(make_response({"message":f"id {id} not found!"}, 404))
return goal

Choose a reason for hiding this comment

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

Keep in mind that these are two very similar methods! I wonder if there is a way to combine the two into one! Great job separating it into its own helper file though!



24 changes: 23 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,26 @@


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, nullable=False)
tasks = db.relationship("Task", back_populates="goal", lazy=True)


def to_dict(self):
return{
"id":self.goal_id,
"title":self.title
}

#create function for title
@classmethod
def create(cls, request_body):

Choose a reason for hiding this comment

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

Just for consistency's sake, it's a good idea to use a method name that is closer to from_dict to mirror our to_dict method!

return cls(
title = request_body["title"]
)


#update function for title
def update(self, request_body):
self.title=request_body["title"]

Choose a reason for hiding this comment

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

I love this method! I'm excited to see how you use it!


45 changes: 44 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
from app import db
from datetime import datetime


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, default=None)

Choose a reason for hiding this comment

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

In this situation, the default for nullable is True, so we do not have to specify that particular constraint! Similarly, when nullable is set to True, the default for default is None, so that also doesn't need to be specified!

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

def to_dict(self):
is_complete=True if self.completed_at else False

Choose a reason for hiding this comment

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

Well done with this specification!


task_dict = {
"id":self.task_id,
"title":self.title,
"description":self.description,
"is_complete":is_complete
}
if self.goals_id:
task_dict["goal_id"] = self.goals_id

return task_dict

#create function for title & description
@classmethod
def create(cls, request_body):

Choose a reason for hiding this comment

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

Same idea as before, I would change this method name to something like from_dict to keep things consistent!

return cls(
title = request_body["title"],
description = request_body["description"]
)

#update function for title & description
def update(self, request_body):
self.title=request_body["title"]
self.description=request_body["description"]


#patch function for is_completed
def patch_complete(self):
self.completed_at=datetime.utcnow()

#patch function for is_completed when incomplete
def patch_incomplete(self):
self.completed_at=None

235 changes: 234 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1 +1,234 @@
from flask import Blueprint
from flask import Blueprint, jsonify, make_response, request

Choose a reason for hiding this comment

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

When we have multiple models, our number of routes really starts to add up, so don't be afraid to separate them into multiple files!

import os, requests
from app import db
from app.models.task import Task
from app.models.goal import Goal
from app.helper import validate_id, validate_goal

tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks")
goals_bp = Blueprint("goals", __name__, url_prefix="/goals")


#get all tasks-"/tasks"-GET(read)
@tasks_bp.route("", methods=["GET"])
def get_all_tasks():
if request.args.get("sort") == "asc":
tasks = Task.query.order_by(Task.title.asc())
elif request.args.get("sort") == "desc":
tasks = Task.query.order_by(Task.title.desc())
else:
tasks = Task.query.all()
tasks_response = []
for task in tasks:
tasks_response.append(task.to_dict())

Choose a reason for hiding this comment

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

This might be a good candidate for a list comprehension!

return jsonify(tasks_response), 200


#get one tasks-"/tasks/1"-GET(read)
@tasks_bp.route("/<id>", methods=["GET"])
def get_task(id):
task = validate_id(id)
return jsonify({"task":task.to_dict()}), 200


#create task-"/tasks"-POST(create)
@tasks_bp.route("", methods=["POST"])
def create_task():
request_body = request.get_json()
try:
new_task = Task.create(request_body)
except KeyError:
return make_response({"details": "Invalid data"}), 400

db.session.add(new_task)
db.session.commit()
return jsonify({"task":new_task.to_dict()}), 201

Choose a reason for hiding this comment

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

Great job with the error handling here! We never know if the user's request body contains everything we are asking for!



#update task-"tasks/1"-PUT(update)
@tasks_bp.route("/<id>", methods=["PUT"])
def update_task(id):
task = validate_id(id)
request_body = request.get_json()
task.update(request_body)
db.session.commit()
return jsonify({"task":task.to_dict()}), 200

Choose a reason for hiding this comment

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

I love your use of the update method you created in the task model! That being said, this would be another good candidate for some error handling!



#delete task-"tasks/1"-DELETE(delete)
@tasks_bp.route("/<id>", methods=["DELETE"])
def delete_task(id):
task = validate_id(id)
db.session.delete(task)
db.session.commit()
return jsonify({"details": f'Task {id} "{task.to_dict()["title"]}" successfully deleted'}), 200


#patch task-"tasks/1/mark_complete"-PATCH(update)
@tasks_bp.route("/<id>/mark_complete", methods=["PATCH"])
def mark_complete(id):
task = validate_id(id)
request_body = request.get_json()
task.patch_complete()
db.session.commit()

SLACK_API_URL = "https://slack.com/api/chat.postMessage"

Choose a reason for hiding this comment

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

As a constant, it might be a good idea to move this variable outside all your routes!

if "SLACKBOT_TOKEN" is None:

Choose a reason for hiding this comment

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

"SLACKBOT_TOKEN" is currently being read as a string, so this conditional will never be triggered. The more appropriate approach would be to use the os.environ.get("SLACKBOT_TOKEN").

return jsonify({'error': 'Slackbot token not set'}), 500
headers = {"Authorization": os.environ.get("SLACKBOT_TOKEN")}
params = {
'channel': 'task-notification',
'text': f"Someone just completed the task {task.title}",
}
response = requests.post(SLACK_API_URL, headers=headers, json=params)
if not response.ok:
return jsonify({'error': 'Failed to send Slack message'}), 500

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


#patch task-"tasks/1/mark_incomplete"-PATCH(update)
@tasks_bp.route("/<id>/mark_incomplete", methods=["PATCH"])
def mark_incomplete(id):
task = validate_id(id)
request_body = request.get_json()
task.patch_incomplete()
db.session.commit()
return jsonify({"task":task.to_dict()}), 200

Choose a reason for hiding this comment

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

Another great candidate for error handling!
















Choose a reason for hiding this comment

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

Don't forget to remove some of this extraneous space before submitting a pull request!

#==========================WAVE 5 GOALS BELOW==========================





#get all goals-"/tasks"-GET(read)
@goals_bp.route("", methods=["GET"])
def get_all_goals():
goals = Goal.query.all()
goals_response = []
for goal in goals:
goals_response.append(goal.to_dict())

Choose a reason for hiding this comment

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

Don't forget we can use list comprehensions here!

return jsonify(goals_response), 200


#get one goals-"/goals/1"-GET(read)
@goals_bp.route("/<id>", methods=["GET"])
def get_goal(id):
goal = validate_goal(id)
return jsonify({"goal":goal.to_dict()}), 200


#create goal-"/goals"-POST(create)
@goals_bp.route("", methods=["POST"])
def create_goal():
request_body = request.get_json()
try:
new_goal = Goal.create(request_body)
except KeyError:
return make_response({"details": "Invalid data"}), 400

db.session.add(new_goal)
db.session.commit()
return jsonify({"goal":new_goal.to_dict()}), 201

Choose a reason for hiding this comment

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

Great job with error handling again!



#update goal-"goals/1"-PUT(update)
@goals_bp.route("/<id>", methods=["PUT"])
def update_goal(id):
goal = validate_goal(id)
request_body = request.get_json()
goal.update(request_body)
db.session.commit()
return jsonify({"goal":goal.to_dict()}), 200

Choose a reason for hiding this comment

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

Don't forget your error handling here!



#delete goal-"goals/1"-DELETE(delete)
@goals_bp.route("/<id>", methods=["DELETE"])
def delete_goal(id):
goal = validate_goal(id)
db.session.delete(goal)
db.session.commit()
return jsonify({"details": f'Goal {id} "{goal.to_dict()["title"]}" successfully deleted'}), 200


#patch goal-"goals/1/mark_complete"-PATCH(update)
@goals_bp.route("/<id>/mark_complete", methods=["PATCH"])
def mark_complete(id):
goal = validate_id(id)
request_body = request.get_json()
goal.patch_complete()
db.session.commit()
return jsonify({"goal":goal.to_dict()}), 200

Choose a reason for hiding this comment

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

And more error handling here!



#patch goal-"goals/1/mark_incomplete"-PATCH(update)
@goals_bp.route("/<id>/mark_incomplete", methods=["PATCH"])
def mark_incomplete(id):
goal = validate_id(id)
request_body = request.get_json()
goal.patch_incomplete()
db.session.commit()
return jsonify({"goal":goal.to_dict()}), 200













# #=================wave6==================

#add tasks to goal-"/goal_id/tasks"-POST(create)
@goals_bp.route("/<goal_id>/tasks", methods=["POST"])
def add_tasks_to_goal(goal_id):
goal = validate_goal(goal_id)
request_body = request.get_json()

for task_id in request_body["task_ids"]:
task = validate_id(task_id)
goal.tasks.append(task)

db.session.commit()

task_ids = [task.task_id for task in goal.tasks]

response_body = {
"id": goal.goal_id,
"task_ids": task_ids
}
return jsonify(response_body), 200

#get tasks from goal-"/goal_id/tasks"-GET(read)
@goals_bp.route("/<goal_id>/tasks", methods=["GET"])
def get_tasks_from_goal_id(goal_id):
goal = validate_goal(goal_id)
task_list= [task.to_dict() for task in goal.tasks]

response_body = {
"id": goal.goal_id,
"title": goal.title,
"tasks": task_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