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
14 changes: 12 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ def create_app(test_config=None):
app = Flask(__name__)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False



if test_config is None:
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_DATABASE_URI")
# app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
# "SQLALCHEMY_DATABASE_URI")
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get(
"RENDER_DATABASE_URI")
else:
app.config["TESTING"] = True
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
Expand All @@ -30,5 +34,11 @@ def create_app(test_config=None):
migrate.init_app(app, db)

# Register Blueprints here
from .routes.task_routes import bp
app.register_blueprint(bp)

from .routes.goal_routes import bp
app.register_blueprint(bp)
# db.create_all()

return app
13 changes: 11 additions & 2 deletions app/models/goal.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
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, nullable=False)
tasks = db.relationship("Task", back_populates="goal", lazy=True)

def to_dict(self):
return (dict(
id=self.id,
title=self.title
))


36 changes: 34 additions & 2 deletions app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
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, autoincrement=True)
title = db.Column(db.String, nullable=False)
description = db.Column(db.String, nullable=False)
completed_at = db.Column(db.DateTime, nullable=True)
goal = db.relationship("Goal", back_populates="tasks")
goal_id = db.Column(db.Integer, db.ForeignKey("goal.id"))

def to_dict_with_goal(self):
if not self.completed_at:
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 👍🏽

self.is_complete = False
return (dict(
id=self.id,
title=self.title,
description=self.description,
is_complete=self.is_complete,
goal_id=self.goal_id
))
def to_dict(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.

Rather than have two functions that do nearly the same thing, why not adopt the same approach of using conditional logic to set the goal_id?

if not self.completed_at:
self.is_complete = False
return (dict(
id=self.id,
title=self.title,
description=self.description,
is_complete=self.is_complete,
))

@classmethod
def from_dict(cls, data_dict):
return cls(
title = data_dict["title"],
description = data_dict["description"],
is_complete = data_dict["completed_at"]
)
1 change: 0 additions & 1 deletion app/routes.py

This file was deleted.

81 changes: 81 additions & 0 deletions app/routes/goal_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from flask import Blueprint, make_response, jsonify, request, abort
from app.models.task import Task
from app.models.goal import Goal
from app import db
from .route_helpers import validate_model

bp = Blueprint("goals", __name__, url_prefix="/goals")


@bp.route("", methods=["GET"])
def get_all_goals():
goals = Goal.query.all()
goals_list = [goal.to_dict() for goal in goals]

return jsonify(goals_list), 200


@bp.route("", methods=["POST"])
def create_a_goal():
request_body = request.get_json()
if "title" not in request_body or not request_body["title"]:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thorough!

abort(make_response({"details": "Invalid data"}, 400))
new_goal = Goal(
title=request_body["title"]
)
db.session.add(new_goal)
db.session.commit()

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


@bp.route("/<id>", methods=["GET"])
def get_one_goal(id):
goal = validate_model(Goal, id)

return {"goal": goal.to_dict()}, 200


@bp.route("/<id>", methods=["PUT"])
def update_a_goal(id):
goal = validate_model(Goal, id)
request_body = request.get_json()
goal.title = request_body["title"]

db.session.commit()

return {"goal": goal.to_dict()}, 200


@bp.route("/<id>", methods=["DELETE"])
def delete_a_goal(id):
goal = validate_model(Goal, id)
db.session.delete(goal)
db.session.commit()

return make_response({'details': f'Goal {goal.id} "{goal.title}" successfully deleted'}), 200


@bp.route("/<id>/tasks", methods=["GET"])
def get_all_tasks_for_one_goal(id):
goal = validate_model(Goal, id)
tasks_list = [(dict(id=task.id, title=task.title, description=task.description,
is_complete=False if task.completed_at is None else True, goal_id=goal.id)) for task in goal.tasks]

return {"id": goal.id, "title": goal.title, "tasks": tasks_list}, 200


@bp.route("/<id>/tasks", methods=["POST"])
def post_task_ids_to_goal(id):
goal = validate_model(Goal, id)
request_body = request.get_json()
if "task_ids" not in request_body:
abort(make_response({"details": "Invalid data"}, 400))

task_ids = request_body["task_ids"]
tasks_list = [Task.query.get(task_id) for task_id in task_ids]
goal.tasks = tasks_list

db.session.commit()

return {"id": goal.id, "task_ids": task_ids}, 200
17 changes: 17 additions & 0 deletions app/routes/route_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from flask import abort, make_response


def validate_model(cls, id):
try:
id = int(id)
except:
abort(make_response(
{"details": f"{cls.__name__} number {id} not valid"}, 400))

model = cls.query.get(id)

if not model:
abort(make_response(
{"details": f"{cls.__name__} number {id} was not found"}, 404))

return model
118 changes: 118 additions & 0 deletions app/routes/task_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from flask import Blueprint, make_response, jsonify, request, abort
from app.models.task import Task
from app import db
from datetime import datetime
import requests
from .route_helpers import validate_model
import os
from dotenv import load_dotenv

load_dotenv()

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


@bp.route("", methods=["GET"])
def get_all_tasks():
sorted_query = request.args.get("sort")
if sorted_query == "asc":
tasks = Task.query.order_by("title")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Assuming this order_by does ascending by default, without you having to explicitly call order_by(Task.title.asc())? Also, can the .all() be left off of the order_by queries (i.e. does it return what all() returns by default?

elif sorted_query == "desc":
tasks = Task.query.order_by(Task.title.desc())
else:
tasks = Task.query.all()

tasks_list = [task.to_dict() for task in tasks]

return jsonify(tasks_list), 200


@bp.route("", methods=["POST"])
def create_task():
request_body = request.get_json()
if "title" not in request_body or not request_body["title"] or "description" not in request_body or not request_body["description"]:
abort(make_response({"details": "Invalid data"}, 400))

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

db.session.add(new_task)
db.session.commit()

return {"task": new_task.to_dict()}, 201


@bp.route("/<id>", methods=["GET"])
def get_one_task(id):
task = validate_model(Task, id)
if task.goal_id:
return {"task": task.to_dict_with_goal()}, 200
return {"task": task.to_dict()}, 200


@bp.route("/<id>", methods=["PUT"])
def update_task(id):
task = validate_model(Task, id)
request_body = request.get_json()
is_complete = request_body.get("is_complete", False)

task.title = request_body["title"]
task.description = request_body["description"]
task.is_complete = is_complete

db.session.commit()

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


@bp.route("/<id>", methods=["DELETE"])
def delete_task(id):
task = validate_model(Task, id)

db.session.delete(task)
db.session.commit()

return {"details": f'Task {id} "{task.title}" successfully deleted'}, 200


@bp.route("/<id>/mark_complete", methods=["PATCH"])
def complete_task(id):
task = validate_model(Task, id)
if not task.completed_at:
task.completed_at = datetime.now()
task.is_complete = True

PATH = "https://slack.com/api/chat.postMessage"
Authorization = os.environ.get(
"Authorization")
text = f"Someone just completed the task {task.title}"

headers = {
"Authorization": Authorization,
"format": "json"
}

body = {
"channel": "task-notifications",
"text": text,
}
requests.post(PATH, headers=headers, json=body)

db.session.commit()

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


@bp.route("/<id>/mark_incomplete", methods=["PATCH"])
def incomplete_task(id):
task = validate_model(Task, id)

if task.completed_at:
task.completed_at = None
task.is_complete = False

db.session.commit()

return {"task": task.to_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.
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