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

# Cython debug symbols
cython_debug/
cython_debug/

venvsource/
17 changes: 15 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
import os
from dotenv import load_dotenv



db = SQLAlchemy()
migrate = Migrate()
load_dotenv()
Expand All @@ -24,11 +27,21 @@ def create_app(test_config=None):

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


db.init_app(app)
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



9 changes: 9 additions & 0 deletions app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,12 @@

class Goal(db.Model):
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 +10 to +14
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice helper method!


36 changes: 35 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,38 @@


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)


def is_complete(self):
if self.completed_at is None:
is_complete = False
else:
is_complete = True
return is_complete

def to_json(self):
if self.goal_id:
return {
"id": self.task_id,
"goal_id": self.goal_id,
"title": self.title,
"description": self.description,
"is_complete": self.is_complete()
}
else:
return {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": self.is_complete()
}
Comment on lines +13 to +35
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great helper method/functions here to return a dictionary. There's a way to refactor this part of the code that I'd love to show you during our 1:1.


def from_json(self, input_data):
return self(title=input_data["title"],
description=input_data["description"],
completed_at=input_data["completed_at"])
269 changes: 268 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,269 @@
from flask import Blueprint
from app import db
from .models.task import Task
from .models.goal import Goal
from flask import request, Blueprint, make_response, jsonify
from sqlalchemy import desc, asc
import datetime
import os
import requests




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


@tasks_bp.route("", methods=["POST", "GET"])
def handle_tasks():
if request.method == "GET":
sort_query = request.args.get("sort")
if sort_query == "asc":
tasks = Task.query.order_by(Task.title)
elif sort_query == "desc":
tasks = Task.query.order_by(Task.title.desc())
else:
tasks = Task.query.all()

tasks_response = []
for task in tasks:
tasks_response.append({
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": task.is_complete()
})
Comment on lines +30 to +35
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This would have been a good spot to use the helper method in your Task model

return jsonify(tasks_response)

elif request.method == "POST":
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 ({
"details": "Invalid data"
}, 400)
else:
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()

return make_response({
"task": {
"id": new_task.task_id,
"title": new_task.title,
"description": new_task.description,
"is_complete": new_task.is_complete()
Comment on lines +55 to +59
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Another good spot to use the helper method as well

}}, 201)

@tasks_bp.route("/<task_id>", methods=["GET", "PUT", "DELETE"])
def handle_task(task_id):
task = Task.query.get(task_id)
if task is None:
return make_response("", 404)

if request.method == "GET":
if task.goal_id is None:
return {
"task": {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": task.is_complete()
}}
if task.task_id:
return {
"task": {
"id": task.task_id,
"goal_id": task.goal_id,
"title": task.title,
"description": task.description,
"is_complete": task.is_complete()
}}
else:
return {
"task": {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": task.is_complete()
}}
Comment on lines +87 to +93
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You can refactor this section to use your code block like so

Suggested change
return {
"task": {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": task.is_complete()
}}
return {
"task": task.to_json()
}

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

headers = {"Authorization": os.environ.get("SLACK_KEY")}
data = {
"channel": "C021BV8A5V0",
"text": f"Someone just completed the task {task.title}"
}

requests.patch('https://slack.com/api/chat.postMessage',
headers=headers, data=data)
Comment on lines +103 to +110
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great job setting up the slack bot! A future refactor can include putting this code into a helper function.



return {
"task": {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": task.is_complete()
}}
elif request.method == "DELETE":
db.session.delete(task)
db.session.commit()
return {
"details": (f'Task {task.task_id} "{task.title}" successfully deleted')
}

@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def mark_incomplete(task_id):
task = Task.query.get(task_id)
if task is None:
return make_response("", 404)
Comment on lines +129 to +131
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Flask has a method called .get_or_404() that can refine these 3 lines into one!

Suggested change
task = Task.query.get(task_id)
if task is None:
return make_response("", 404)
task = Task.query.get_or_404(task_id)


task.completed_at = None
db.session.commit()

return {
"task": {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": False
}
}

@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def mark_complete(task_id):
task = Task.query.get(task_id)
if task is None:
return make_response("", 404)

task.completed_at = datetime.datetime.now()
db.session.commit()



return {
"task": {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": True
}
}


@goals_bp.route("", methods=["GET"])
def get_goals():
goals = Goal.query.all()
goals_response = []
for goal in goals:
goals_response.append(goal.to_json())
return jsonify(goals_response)

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

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

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

db.session.add(new_goal)
db.session.commit()

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

@goals_bp.route("/<goal_id>", methods=["GET"])
def get_task(goal_id):
goal = Goal.query.get(goal_id)
if goal is None:
return make_response("", 404)

return {
"goal": goal.to_json()
}

@goals_bp.route("/<goal_id>", methods=["PUT"])
def update_task(goal_id):
goal = Goal.query.get(goal_id)
if goal is None:
return make_response("", 404)
Comment on lines +203 to +205
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See comment about get_or_404() above


form_data = request.get_json()

goal.title = form_data["title"]

db.session.commit()

return {
"goal": goal.to_json()
}

@goals_bp.route("/<goal_id>", methods=["DELETE"])
def delete_task(goal_id):
goal = Goal.query.get(goal_id)
if goal is None:
return make_response("", 404)

db.session.delete(goal)
db.session.commit()
return {
"details": (f'Goal {goal.goal_id} "{goal.title}" successfully deleted')
}

@goals_bp.route("/<goal_id>/tasks", methods=["GET"])
def get_goals_tasks(goal_id):

goal = Goal.query.get(goal_id)

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

tasks = Task.query.filter_by(goal_id=goal.goal_id)

tasks_response = []
for task in tasks:
tasks_dict = task.to_json()
tasks_response.append(tasks_dict)

return {
"id": goal.goal_id,
"title": goal.title,
"tasks": tasks_response
}

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

goal = Goal.query.get(goal_id)

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

request_body = request.get_json()

for task_id in request_body["task_ids"]:
task = Task.query.get(task_id)
task.goal_id = goal.goal_id
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

good job finding all the tasks for a specific goal id!


db.session.commit()

return {
"id": goal.goal_id,
"task_ids": request_body["task_ids"]
}
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