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
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()'
10 changes: 7 additions & 3 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
import os
from dotenv import load_dotenv


db = SQLAlchemy()
migrate = Migrate()
load_dotenv()


def create_app(test_config=None):
app = Flask(__name__)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
Expand All @@ -28,7 +26,13 @@ def create_app(test_config=None):

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
30 changes: 29 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,33 @@
from app import db



class Goal(db.Model):
goal_id = db.Column(db.Integer, primary_key=True)
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String)
tasks = db.relationship("Task",backref="goal", lazy=True)


def list_of_task_id(self):
task_ids =[task.id for task in self.tasks]
return task_ids

def to_dict(self):
if self.list_of_task_id():

return {"id":self.id,
"title":self.title,
"task_ids":self.list_of_task_id()
}
else:
return {"id":self.id,
"title":self.title,
}
def task_lists(self):
Comment on lines +12 to +27

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

list=[]
for task in self.tasks:
list.append(task.to_dict())
return list



29 changes: 27 additions & 2 deletions app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
from flask import current_app
from app import db


class Task(db.Model):
task_id = db.Column(db.Integer, primary_key=True)
id = db.Column(db.Integer, primary_key=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.id'))

def check_for_completed_task(self):
if self.completed_at:
return True
return False

def to_dict(self):
if self.goal_id is None:

return {"id":self.id,
"title":self.title,
"description":self.description,
"is_complete":self.check_for_completed_task()
}
else:
return {"id":self.id,
"title":self.title,
"description":self.description,
"is_complete":self.check_for_completed_task(),
"goal_id": self.goal_id
}
Comment on lines +17 to +30

Choose a reason for hiding this comment

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

This can be dried up a bit.

Suggested change
if self.goal_id is None:
return {"id":self.id,
"title":self.title,
"description":self.description,
"is_complete":self.check_for_completed_task()
}
else:
return {"id":self.id,
"title":self.title,
"description":self.description,
"is_complete":self.check_for_completed_task(),
"goal_id": self.goal_id
}
task_dict = {
"id":self.id,
"title":self.title,
"description":self.description,
"is_complete":self.check_for_completed_task()
}
if self.goal_id is not None:
task_dict["goal_id"] = self.goal_id
return task_dict


157 changes: 156 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,157 @@
from flask import Blueprint
from app import db
from app.models.task import Task
from flask import Blueprint, jsonify,request, make_response, abort
from datetime import date
from app.models.goal import Goal
import os
import requests
from dotenv import load_dotenv
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError


def valid_int(number,parameter_type):
try:
int(number)
except:
abort(make_response({"error":f"{parameter_type} must be an int"},400))

def slack_notification():
load_dotenv()
slack_token = os.environ["SLACK_TOKENS"]
client = WebClient(token=slack_token)
try:
response = client.chat_postMessage(
channel ="CNEEJDLAW",
text = "Task completed"
)
except SlackApiError as e:
return jsonify({"Error": "chanel not found"})
Comment on lines +19 to +29

Choose a reason for hiding this comment

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

Great helper function


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

@tasks_bp.route("",methods=["GET"])
def handle_tasks():
sort_query = request.args.get("sort")
if sort_query == "asc":
tasks = Task.query.order_by(Task.title.asc())
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(task.to_dict())
return jsonify(tasks_response),200

@tasks_bp.route("/<task_id>",methods=["GET","put","DELETE"])
def get_task(task_id):
valid_int(task_id, "task_id")
task = Task.query.get_or_404(task_id)
if request.method == "GET":
return jsonify({"task":task.to_dict()}),200
elif request.method == "PUT":
request_body = request.get_json()
if "title" in request_body:
task.title = request_body["title"]
if "description" in request_body:
task.description = request_body["description"]
if "completed_at" in request_body:
task.completed_at = request_body["completed_at"]
db.session.commit()
return jsonify({"task":task.to_dict()}),200
elif request.method == "DELETE":
db.session.delete(task)
db.session.commit()
return jsonify({"details":f'Task {task_id} "{task.title}" successfully deleted'}),200

@tasks_bp.route("",methods=["POST"])
def create_task():
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 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()
return jsonify({"task":new_task.to_dict()}),201

@tasks_bp.route("/<task_id>/mark_complete",methods=["PATCH"])
def mark_complete_task(task_id):
valid_int(task_id, "task_id")
task = Task.query.get_or_404(task_id)
task.completed_at = date.today()
db.session.commit()
slack_notification()
return jsonify({"task":task.to_dict()}),200

@tasks_bp.route("/<task_id>/mark_incomplete",methods=["PATCH"])
def mark_incomplete_task(task_id):
valid_int(task_id, "task_id")
task = Task.query.get_or_404(task_id)
task.completed_at = None
db.session.commit()
return jsonify({"task":task.to_dict()}),200

@goals_bp.route("", methods=["POST"])
def create_goal():
request_body = request.get_json()
if "title" not in request_body:
return jsonify({'details': "Invalid data"}),400
new_goal = Goal(title = request_body["title"])
db.session.add(new_goal)
db.session.commit()
return jsonify({"goal":new_goal.to_dict()}),201

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

@goals_bp.route("/<goal_id>", methods=["GET", "PUT","DELETE"])
def get_goal(goal_id):
valid_int(goal_id,"goal_id")
goal = Goal.query.get_or_404(goal_id)
if request.method == "GET":
return jsonify({"goal":goal.to_dict()}),200
elif request.method == "DELETE":
db.session.delete(goal)
db.session.commit()
return jsonify({"details":f"Goal {goal_id} \"{goal.title}\" successfully deleted"})
elif request.method == "PUT":
request_body = request.get_json()
goal.title = request_body["title"]
db.session.commit()
return jsonify({"goal":goal.to_dict()}),200

@goals_bp.route("/<goal_id>/tasks", methods=["POST"])
def post_task_ids_to_goal(goal_id):
valid_int(goal_id,"goal_id")
request_body = request.get_json()
goal = Goal.query.get(goal_id)
task_ids = request_body["task_ids"]
for task_id in task_ids:
task = Task.query.get(task_id)
goal.tasks.append(task)
db.session.commit()
return jsonify({"id":goal.id, "task_ids": [task.id for task in goal.tasks]}),200

@goals_bp.route("/<goal_id>/tasks", methods=["GET"])
def get_tasks_for_goal(goal_id):
valid_int(goal_id,"goal_id")
goal = Goal.query.get_or_404(goal_id)
response_body = {"id":goal.id,
"title":goal.title,
"tasks":goal.task_lists()
}
print(response_body)
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.
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