Skip to content
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ share/python-wheels/
.installed.cfg
*.egg
MANIFEST
.env
venv
__pycache__

# PyInstaller
# Usually these files are written by a python script from a template
Expand Down
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()'
14 changes: 14 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,28 @@ def create_app(test_config=None):
app.config["TESTING"] = True
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_TEST_DATABASE_URI")

app.config['JSON_SORT_KEYS'] = False






# Import models here for Alembic setup
from app.models.task import Task
from app.models.goal import Goal
from .routes import tasks_bp
from .routes import goals_bp

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




# Register Blueprints here
app.register_blueprint(tasks_bp)
app.register_blueprint(goals_bp)

return app
35 changes: 34 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,38 @@
from app import db


class Goal(db.Model):
class Goal(db.Model): # Goal is the parent class
goal_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
tasks = db.relationship('Task', backref='goal', lazy=True)


def to_json(self):

return {
"id": self.goal_id,
"title": self.title
}
Comment on lines +11 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👍 I like these helper methods


def to_json_three(self):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why three?


task_list = []
for task in self.tasks:
if task.completed_at:
complete = True
else:
complete = False

task_list.append({
"id": task.task_id,
"goal_id": task.goal_id,
"title": task.title,
"description": task.description,
"is_complete": complete
})

return {
"id": self.goal_id,
"title": self.title,
"tasks": task_list
}
31 changes: 30 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,35 @@
from flask import current_app
from app import db
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)
description = db.Column(db.String)
completed_at = db.Column(db.DateTime, nullable=True)
goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'), nullable=True)

# created a helper function to convert completed_at to is_complete.
def to_json(self):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👍

if self.completed_at:
complete = True
else:
complete = False

regular_response = {

"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": complete
}

if self.goal_id is not None:
regular_response["goal_id"] = self.goal_id

return regular_response




232 changes: 231 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,232 @@
from flask import Blueprint
from app import db
from app.models.task import Task
from flask import request, Blueprint, make_response
from flask import jsonify
from sqlalchemy import asc, desc
import time
import datetime
import requests
from flask import current_app as app
import os
from app.models.goal import Goal

path = "https://slack.com/api/chat.postMessage"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Make variables you want to stay stuck to a specific value, in all capital letters. We normally make constants in all caps. Also change the name to specify which path you want.

Suggested change
path = "https://slack.com/api/chat.postMessage"
SLACK_PATH = "https://slack.com/api/chat.postMessage"


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

@tasks_bp.route("", methods=["POST", "GET"], strict_slashes=False)
def tasks():
Comment on lines +17 to +18
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Small readability suggestion, it could be good to break up these large functions into smaller functions. So one function to handle the GET request and the other handles the POST request.


if request.method == "GET":
task_order = request.args.get("sort")
if task_order == None:
tasks = Task.query.all() # Task is the model and query is a class method (query is getting all task)
elif task_order == "asc":
tasks = Task.query.order_by(asc(Task.title))
elif task_order == "desc":
tasks = Task.query.order_by(desc(Task.title))

tasks_response = []
for task in tasks:

tasks_response.append(task.to_json())


return jsonify(tasks_response)
# using the "PUT" to add a new task
else:
request_body = request.get_json()
if "title" not in request_body or "description" not in request_body or "completed_at" not in request_body:
return make_response(jsonify({"details": "Invalid data"}), 400)

task = Task(title = request_body["title"],
description = request_body["description"],
completed_at = request_body["completed_at"])


db.session.add(task)
db.session.commit()

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



@tasks_bp.route("/<task_id>", methods=["GET", "PUT", "DELETE"], strict_slashes=False)
def handle_task(task_id):
# Try to find the task with the given id

task = Task.query.get(task_id)

if task is None:
return make_response("", 404)

if request.method == "GET":

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

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()

return jsonify({"task": task.to_json()})



elif request.method == "DELETE":
db.session.delete(task)
db.session.commit()
return make_response({"details": (f"Task {task.task_id} \"{task.title}\" successfully deleted")})


return {
"message": f"Task with id {task_id} was not found",
"success": False,
}, 404




@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"], strict_slashes=False)
def task_complete(task_id):
task = Task.query.get(task_id)

if task is None:
return make_response("", 404)
elif task:
task.completed_at = datetime.datetime.now()
# if task mark a complete then send a message
key = os.environ.get("AUTHORIZATION")

query_params = {
"text": f"Someone just completed the task {task.title}",
"channel": "task-notifications"

}

query_headers = {
"authorization": f"Bearer {key}"
}
response = requests.post(path, params=query_params, headers=query_headers )
response_body = response.json()
Comment on lines +103 to +115
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Just a note: This would make a great helper function.


print(response)


Comment on lines +116 to +119
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
print(response)

db.session.commit()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I would also send the slack message, after you have successfully saved the task to the database.


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

@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"], strict_slashes=False)
def task_incomplete(task_id):
task = Task.query.get(task_id)

if task is None:
return make_response("", 404)
elif task:
task.completed_at = None

db.session.commit()

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


goals_bp = Blueprint("goals", __name__, url_prefix="/goals")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I suggest you define the Blueprints together at the top, just to make it easier to review all the blueprints available.




@goals_bp.route("", methods=["POST", "GET"], strict_slashes=False)
def goals():

if request.method == "GET":
goals = Goal.query.all()
goals_response = []
for goal in goals:

goals_response.append(goal.to_json())


return jsonify(goals_response)
# using the "PUT" to add a new goal
else:
request_body = request.get_json()
if "title" not in request_body:
return make_response(jsonify({"details": "Invalid data"}), 400)

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




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

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



@goals_bp.route("/<goal_id>", methods=["GET", "PUT", "DELETE"], strict_slashes=False)
def handle_goal(goal_id):
# Try to find the goal with the given id

goal = Goal.query.get(goal_id)

if goal is None:
return make_response("", 404)

if request.method == "GET":
return jsonify({"goal": goal.to_json()}),200

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

goal.title = form_data["title"]

db.session.commit()
return jsonify({"goal": goal.to_json()})



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

return {
"message": f"Goal with id {goal_id} was not found",
"success": False,
}, 404


@goals_bp.route("/<goal_id>/tasks", methods=["POST"], strict_slashes=False)
def post_goals_task(goal_id):

form_data = request.get_json()

for task_id in form_data["task_ids"]:
task = Task.query.get(task_id)
task.goal_id = goal_id

db.session.commit()

return jsonify({"id": int(goal_id), "task_ids": form_data["task_ids"]}),200
@goals_bp.route("/<goal_id>/tasks", methods=["GET"], strict_slashes=False)
def get_goals_task(goal_id):
Comment on lines +217 to +218
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Small suggestion, add a blank line between route definitions

Suggested change
@goals_bp.route("/<goal_id>/tasks", methods=["GET"], strict_slashes=False)
def get_goals_task(goal_id):
@goals_bp.route("/<goal_id>/tasks", methods=["GET"], strict_slashes=False)
def get_goals_task(goal_id):


if request.method == "GET":
goal = Goal.query.get(goal_id)
if goal is None:
return make_response("", 404)


return jsonify(goal.to_json_three())






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