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

Accelerate-Christina S #10

Open
wants to merge 13 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()'
8 changes: 8 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,15 @@ def create_app(test_config=None):

db.init_app(app)
migrate.init_app(app, db)

from app.models.task import Task
from app.models.goal import Goal

# 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
4 changes: 4 additions & 0 deletions app/models/goal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from flask import current_app
from app import db
from sqlalchemy import ForeignKey, update
from sqlalchemy.orm import relationship
Comment on lines +3 to +4
Copy link

Choose a reason for hiding this comment

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

remove these imports



class Goal(db.Model):
goal_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
tasks = db.relationship('Task', backref='tasks', lazy=True)
7 changes: 7 additions & 0 deletions app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from flask import current_app
from app import db
from sqlalchemy.orm import relationship
from sqlalchemy import ForeignKey, update
Comment on lines +3 to +4
Copy link

Choose a reason for hiding this comment

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

remove these imports



class Task(db.Model):
task_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.VARCHAR(50))
description = db.Column(db.String)
completed_at = db.Column(db.DateTime, nullable=True, default=None)
is_complete = db.Column(db.Boolean, default=False)
Copy link

Choose a reason for hiding this comment

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

I think adding this here creates confusion and is why some of your tests for mark complete/incomplete are failing. I would remove this line and just add an instance method for self.completed_at. It could be here in the Task model or in routes.py.

Suggested change
is_complete = db.Column(db.Boolean, default=False)

an example of how that could look
def build_dict_from_task(task): is_complete = True if task.completed_at else False dict = { "id": task.task_id, "title": task.title, "description": task.description, "is_complete": is_complete }

goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'), nullable=True)
261 changes: 260 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,261 @@
from flask import Blueprint
from app import db
from app.models.task import Task
from app.models.goal import Goal
from flask import request, Blueprint, make_response, jsonify
from datetime import datetime
import os
import json, requests
from dotenv import load_dotenv


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

load_dotenv()

def post_message_to_slack(text):
SLACK_TOKEN = os.environ.get('SLACKBOT_TOKEN')
slack_path = "https://slack.com/api/chat.postMessage"
query_params ={
'channel': 'task-notifications',
'text': text
}
headers = {'Authorization': f"Bearer {SLACK_TOKEN}"}
requests.post(slack_path, params=query_params, headers=headers)

@goals_bp.route("", methods=["GET", "POST"])
def handle_goals():
if request.method == "GET":
goals = Goal.query.all()
goal_response = []
for goal in goals:
goal_response.append({
"title": goal.title,
"id": goal.goal_id
})
return jsonify(goal_response), 200

elif request.method == "POST":
request_body = request.get_json()
title = request_body.get("title")
if not title:
return jsonify({"details": "Invalid data"}), 400

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

commited_goal = {"goal":
{"id": new_goal.goal_id,
"title": new_goal.title
}}

return (commited_goal), 201
Comment on lines +27 to +53
Copy link

Choose a reason for hiding this comment

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

💃🏽


@goals_bp.route("/<goal_id>", methods=["GET", "PUT", "DELETE"])
def handle_goal(goal_id):
goal = Goal.query.get_or_404(goal_id)

if request.method == "GET":
selected_goal = {"goal":
{"id": goal.goal_id,
"title": goal.title
}
}
return selected_goal
Copy link

Choose a reason for hiding this comment

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

just to be consistent I would add

Suggested change
return selected_goal
return jsonify(selected_goal), 200


elif request.method == "PUT":
form_data = request.get_json()

goal.title = form_data["title"]

db.session.commit()

commited_goal = {"goal":
{"id": goal.goal_id,
"title": goal.title
}
}
return jsonify(commited_goal), 200

elif request.method == "DELETE":
db.session.delete(goal)
db.session.commit()
return jsonify(
{f"details": f'Goal {goal.goal_id} "{goal.title}" successfully deleted'}
)

@goals_bp.route("/<goal_id>/tasks", methods=["GET", "POST"])
def handle_goal_tasks(goal_id):
goal = Goal.query.get_or_404(goal_id)
if request.method == "GET":
tasks = []
for task in goal.tasks:
single_task= {
"id": task.task_id,
"goal_id": goal.goal_id,
Copy link

Choose a reason for hiding this comment

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

Here I would add a check if the task has a goal id. Something like
if task.goal_id: dict["goal_id"] = task.goal_id]
so its only added to the dictionary if there is a goal_id

"title": task.title,
"description": task.description,
"is_complete": bool(task.completed_at)
}
tasks.append(single_task)
return make_response ({
"id": goal.goal_id,
"title": goal.title,
"tasks": tasks
})

if request.method == "POST":
goal.tasks =[]

form_data = request.get_json()
task_ids = form_data['task_ids']

for t_id in task_ids:
task = Task.query.get_or_404(t_id)
goal.tasks.append(task)

db.session.add(goal)
db.session.commit()

response_task_ids = []
for task in goal.tasks:
response_task_ids.append(task.task_id)

return {
"id": goal.goal_id,
"task_ids": response_task_ids
}

@tasks_bp.route("", methods=["POST", "GET"])
def handle_tasks():
if request.method == "GET":
title_query = request.args.get("title")
if title_query:
tasks = Task.query.filter(Task.title.ilike(f'%{title_query}%'))
Copy link

Choose a reason for hiding this comment

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

ouu interesting what made you choose ilike() instead of filter_by()

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

tasks_response = []
for task in tasks:
tasks_response.append({
"description": task.description,
"is_complete": bool(task.completed_at),
"id": task.task_id,
"title": task.title
})
return jsonify(tasks_response)

elif request.method == "POST":
request_body = request.get_json()
title = request_body.get("title")
description = request_body.get("description")
if not title or not description 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()
commited_task = {"task":
{"id": new_task.task_id,
"title": new_task.title,
"description": new_task.description,
"is_complete": bool(new_task.completed_at)}}

return jsonify(commited_task), 201

@tasks_bp.route("/<task_id>", methods=["GET", "PUT", "DELETE"])
def handle_task(task_id):
task = Task.query.get_or_404(task_id)

if request.method == "GET":
if task == None:
return make_response("No matching task found", 404)
if request.method == "GET":
if task.goal_id == None:
selected_task = {"task":
{"task_id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": task.is_complete
}
}
return selected_task
else:
return make_response ({
"task": {
"id": task.task_id,
"goal_id": task.goal_id,
"title": task.title,
"description": task.description,
"is_complete": task.is_complete
}
})
Comment on lines +181 to +203
Copy link

Choose a reason for hiding this comment

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

good check


elif request.method == "PUT":
form_data = request.get_json()

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

db.session.commit()

commited_task = {"task":
{"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": bool(task.completed_at)
}
}
return jsonify(commited_task), 200

elif request.method == "DELETE":
db.session.delete(task)
db.session.commit()
return jsonify(
{f"details": 'Task 1 "Go on my daily walk 🏞" successfully deleted'}
)

@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def mark_task_completed(task_id):
task = Task.query.get_or_404(task_id)
task.completed_at = datetime.now()

db.session.commit()
slack_message = f"Someone just completed the task {task.title}"
post_message_to_slack(slack_message)

completed_task = {"task":
{"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": bool(task.completed_at)
Copy link

@tgoslee tgoslee Jun 19, 2021

Choose a reason for hiding this comment

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

if this is marked complete then "is_complete" would equal to True

}
}
return (completed_task), 200

@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def mark_task_incomplete(task_id):
task = Task.query.get_or_404(task_id)
task.completed_at = None

db.session.commit()
incomplete_task = {"task":
{"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": bool(task.completed_at)
Copy link

@tgoslee tgoslee Jun 19, 2021

Choose a reason for hiding this comment

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

if this is marked incomplete then "is_complete" would equal False

}
}
return jsonify(incomplete_task), 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.
45 changes: 45 additions & 0 deletions migrations/alembic.ini
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
Loading