Skip to content

Lions - Jessica Ambriz-Madrigal #127

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

Open
wants to merge 18 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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,7 @@ dmypy.json
.pytype/

# Cython debug symbols
cython_debug/
cython_debug/

# files
REMEMEBER.txt
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()'
12 changes: 12 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,17 @@ def create_app(test_config=None):
migrate.init_app(app, db)

# Register Blueprints here
# from app.models.task import Task
from app.routes import task_bp
app.register_blueprint(task_bp)

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


return app





Comment on lines +42 to +46

Choose a reason for hiding this comment

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

Suggested change

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


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='goals', lazy=True)


def to_dict(self):
goal_dict = {
"id":self.goal_id,
"title":self.title
}
return goal_dict
34 changes: 33 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,36 @@


class Task(db.Model):
task_id = db.Column(db.Integer, primary_key=True)
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, default=False)
goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'), nullable = True )
goals = db.relationship('Goal', back_populates='tasks')

def to_dict(self):
if self.completed_at is None:
if self.goal_id is None:
task_dict = {
"id":self.id,
"title":self.title,
"description":self.description,
"is_complete": False
}
else:
task_dict = {
"id":self.id,
"goal_id": self.goal_id,
"title":self.title,
"description":self.description,
"is_complete": False
}
else:
task_dict = {
"id":self.id,
"title":self.title,
"description":self.description,
"is_complete": True
}
return task_dict
Comment on lines +14 to +37

Choose a reason for hiding this comment

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

This seems a little over-complicated. Could you do the basic dictionary in one block and then add key-value pairs based on an if-else?

276 changes: 275 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1 +1,275 @@
from flask import Blueprint
from flask import Blueprint,jsonify, abort, make_response, request
from app import db
from app.models.task import Task
from app.models.goal import Goal
from datetime import datetime
import os
import requests

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

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.

Good 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=["POST"])
def add_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 +30 to +32

Choose a reason for hiding this comment

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

Good validation here. However I think you should also indicate what data is invalid.


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

db.session.add(new_task)

db.session.commit()

return {
"task":{
"id" : new_task.id,
"title" : new_task.title,
"description" : new_task.description,
"is_complete" : new_task.is_complete
}
}, 201
Comment on lines +42 to +49

Choose a reason for hiding this comment

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

This return value might be useful as a helper function for reuse.


@task_bp.route("", methods=["GET"])
def get_all_tasks():
title_param = request.args.get("title")

sort_param = request.args.get("sort")

if not title_param:
tasks = Task.query.all()

if sort_param == "desc":
tasks = Task.query.order_by(Task.title.desc()).all()

if sort_param == "asc":
tasks = Task.query.order_by(Task.title.asc()).all()
Comment on lines +57 to +64

Choose a reason for hiding this comment

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

This might cause Task.query to execute twice making this potentially twice as slow as it needs to be.


response = [task.to_dict() for task in tasks]
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)
return jsonify({"task":Task.to_dict(chosen_task)}), 200

@task_bp.route("/<task_id>", methods=["PUT"])
def update_one_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."}), 400

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

db.session.commit()

return {
"task":{
"id" : chosen_task.id,
"title" : chosen_task.title,
"description" : chosen_task.description,
"is_complete" : chosen_task.is_complete
}
}, 200

@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


@task_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def update_is_complete(task_id):
update_is_valid_id = get_one_task_or_abort(task_id)

update_is_valid_id.completed_at = datetime.today()
db.session.add(update_is_valid_id)
db.session.commit()

SLACK_PATH = "https://slack.com/api/chat.postMessage"
SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN")

headers = {
"Authorization" : f"Bearer {SLACK_BOT_TOKEN}"
}
params = {
"channel": "task-notifications",
"text": f"Someone just completed the task {update_is_valid_id.title}"
}

requests.get(url=SLACK_PATH, headers=headers, params=params)
Comment on lines +117 to +128

Choose a reason for hiding this comment

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

The Slack portion would make a good candidate for a helper function.



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

@task_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def update_incompete(task_id):
task_incompete = get_one_task_or_abort(task_id)
task_incompete.completed_at = None

db.session.add(task_incompete)
db.session.commit()

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


# ###################################################################
goal_bp = Blueprint("goal_bp",__name__, url_prefix="/goals")

def get_one_goal_or_abort(id):
try:
id = int(id)
except ValueError:
response_str = f"Invalid id: `{id}`.Id must be an integer."
abort(make_response(jsonify({'message':response_str}), 400))

matching_goal = Goal.query.get(id)

if not matching_goal:
response_str = f"Goal with id {id} was not found in the database."
abort(make_response(jsonify({'message':response_str}), 404))
return matching_goal

@goal_bp.route("", methods=["POST"])
def add_goal():
request_body = request.get_json()

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

Choose a reason for hiding this comment

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

Again indicating what data is invalid would be useful.


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

db.session.add(new_goal)

db.session.commit()

return {
"goal":{
"id" : new_goal.goal_id,
"title" : new_goal.title
}
}, 201


@goal_bp.route("", methods=["GET"])
def get_all_goals():

goals = Goal.query.all()

response = [Goal.to_dict(goal) for goal in goals]
return jsonify(response), 200


@goal_bp.route("/<id>", methods=["GET"])
def get_one_goal(id):
chosen_goal = get_one_goal_or_abort(id)
goal_dict = {
"goal":{
"id" : chosen_goal.goal_id,
"title" : chosen_goal.title
}
}

return jsonify(goal_dict), 200

@goal_bp.route("/<goal_id>", methods=["PUT"])
def update_one_task(goal_id):
chosen_goal = get_one_goal_or_abort(goal_id)
request_body = request.get_json()

if "title" not in request_body:
return jsonify({"message": "request must include title."}), 400

chosen_goal.title = request_body["title"]

db.session.commit()

return {
"goal":{
"id" : chosen_goal.goal_id,
"title" : chosen_goal.title
}
}, 200

@goal_bp.route("/<goal_id>", methods=["DELETE"])
def delete_one_goal(goal_id):
chosen_goal = get_one_goal_or_abort(goal_id)

db.session.delete(chosen_goal)

db.session.commit()

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

# ###################################################################

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

big_goal_id = get_one_goal_or_abort(goal_id)

request_body = request.get_json()

for task_id in request_body["task_ids"]:
valid_task = get_one_task_or_abort(task_id)
valid_task.goals = big_goal_id
db.session.commit()

big_goal_task_id_list = []
for task in big_goal_id.tasks:
big_goal_task_id_list.append(task.id)

new_goal_with_task = {
"id" : big_goal_id.goal_id,
"task_ids": big_goal_task_id_list
}


return jsonify(new_goal_with_task), 200

# # GET
@goal_bp.route("/<goal_id>/tasks", methods=["GET"])
def get_one_goal_with_tasks(goal_id):
chosen_big_goal_id = get_one_goal_or_abort(goal_id)

big_goal_task_id_list = []
for task in chosen_big_goal_id.tasks:
big_goal_task_id_list.append(Task.to_dict(task))

goal_dict = {
"id" : chosen_big_goal_id.goal_id,
"title" : chosen_big_goal_id.title,
"tasks": big_goal_task_id_list
}

return jsonify(goal_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