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()'
6 changes: 5 additions & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
migrate = Migrate()
load_dotenv()


def create_app(test_config=None):
app = Flask(__name__)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
Expand All @@ -30,5 +29,10 @@ def create_app(test_config=None):
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
11 changes: 10 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,13 @@


class Goal(db.Model):
goal_id = db.Column(db.Integer, primary_key=True)
goal_id = db.Column(db.Integer, primary_key=True, autoincrement= True)
title = db.Column(db.String)
tasks = db.relationship("Task", back_populates="goal") #NEW

def to_dict(self):

return {
"id": self.goal_id,
"title": self.title,
}
29 changes: 28 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
from flask import current_app
from sqlalchemy.orm import backref
from app import db


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')) #NEW
goal = db.relationship("Goal", back_populates= "tasks") #NEW

def is_complete(self):
if not self.completed_at:
is_complete = False
else:
is_complete = True
Comment on lines +15 to +18

Choose a reason for hiding this comment

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

I think you probably meant to clean this up since the code below (line 20) handles this on its own.


return bool(self.completed_at)

def to_dict(self):
# if not self.completed_at:
# is_complete = False
# else:
# is_complete = True

Comment on lines +23 to +27

Choose a reason for hiding this comment

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

Style: It's good practice to clean up commented out code.

return {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": self.is_complete(),
}
234 changes: 233 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,234 @@
from flask import Blueprint
from re import match
from app import db
from flask import Blueprint, request, make_response, jsonify, abort
from app.models.task import Task
from app.models.goal import Goal
from datetime import datetime
import requests
import os



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

#-----------------
#HELPER FUNCTIONS

Choose a reason for hiding this comment

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

Nice helper functions. 😃

def get_task_from_id(id):
try:
id = int(id)
except:
abort(400, {"error": "invalid id"})
return Task.query.get_or_404(id)

def get_goal_from_id(id):
try:
id = int(id)
except:
abort(400, {"error": "invalid id"})
return Goal.query.get_or_404(id)

#-----------------
#CREATE (aka POST)
@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 make_response(jsonify({"details" : "Invalid data"}), 400)

Choose a reason for hiding this comment

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

If you're returning a dictionary you don't need to call jsonify or make_response Flask's default return behavior will work just fine.

Suggested change
return make_response(jsonify({"details" : "Invalid data"}), 400)
return {"details" : "Invalid data"}, 400

When you return a dictionary Flask automatically makes a JSON response. (This isn't true for a list which is why we need jsonify sometimes.)


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(jsonify({"task": new_task.to_dict()}), 201)

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

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

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

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

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

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

goal = Goal.query.get(goal_id)
if goal is None:
return make_response("Invalid Goal ID", 404)

goal_task_ids = []

for task_id in request_body["task_ids"]:
task = Task.query.get(task_id)
task.goal = goal
goal_task_ids.append(task.task_id)
db.session.add(task)
db.session.commit

return jsonify({"id": int(goal_id), "task_ids": goal_task_ids})


#-----------------
#READ ALL (aka GET)
@tasks_bp.route("", methods=["GET"])
def read_all_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)

@goals_bp.route("", methods=["GET"])
def read_all_goals():
goals = Goal.query.all()

goals_response = []
for goal in goals:
goals_response.append(goal.to_dict())
return jsonify(goals_response)

#-----------------
#READ ONE (aka GET)
@tasks_bp.route("/<id>", methods=["GET"])
def read_one_task(id):
task = get_task_from_id(id)
request_body = request.get_json()

if not task.goal_id:
return make_response(jsonify({"task" : task.to_dict()}),200)

else:
task_goal_response = {
"id": task.task_id,
"goal_id": task.goal_id,
"title": task.title,
"description": task.description,
"is_complete": task.is_complete(),
}
return jsonify({"task" : task_goal_response})

@goals_bp.route("/<id>", methods=["GET"])
def read_one_task(id):
goal = get_goal_from_id(id)
return make_response(jsonify({"goal" : goal.to_dict()}),200)

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

for task in goal.tasks:
goal_tasks_response.append({
"id": task.task_id,
"goal_id": task.goal_id,
"title": task.title,
"description": task.description,
"is_complete": task.is_complete(),
})

return (jsonify({"id": int(goal_id), "title": goal.title, "tasks": goal_tasks_response}), 200)

#-----------------
#UPDATE
@tasks_bp.route("/<id>", methods=["PUT"])
def update_task(id):
task = get_task_from_id(id)
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"]

db.session.commit()

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

@goals_bp.route("/<id>", methods=["PUT"])
def update_goal(id):
goal = get_goal_from_id(id)
request_body = request.get_json()

goal.title = request_body["title"]

db.session.commit()

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

#-----------------
#UPDATE --TASK-- COMPLETION STATUS
@tasks_bp.route("/<id>/<completion_status>", methods=["PATCH"])
def mark_complete(id, completion_status):
task = get_task_from_id(id)
#task_dict = {}

if completion_status == "mark_complete":
task.completed_at = datetime.date

#SLACK MESSAGING
SLACK_MSG_URL = 'https//slack.com/api/com.postMessage'
SLACK_MSG_CHANNEL = 'task-notifications'
SLACK_BOT_USERNAME = 'AliesBot'
SLACK_TOKEN = os.environ.get("SLACK_TOKEN")
slack_msg = "Someone did a thing!"

requests.post(SLACK_MSG_URL,{
"token": SLACK_TOKEN,
"channel": SLACK_MSG_CHANNEL,
"text": slack_msg,
"username": SLACK_BOT_USERNAME
})

if completion_status == "mark_incomplete":
task.completed_at = None

#task_dict["task"] = task.to_dict()

Choose a reason for hiding this comment

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

You didn't commit before turning from here.

Things will look right in your response but won't be saved between requests.

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

#-----------------
#DELETE
@tasks_bp.route("/<id>", methods=["DELETE"])
def delete_task(id):
task = get_task_from_id(id)

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

return make_response(jsonify({'details': f'Task {task.task_id} "{task.title}" successfully deleted'}),200)

@goals_bp.route("/<id>", methods=["DELETE"])
def delete_goal(id):
goal = get_goal_from_id(id)

db.session.delete(goal)
db.session.commit()

return make_response(jsonify({'details': f'Goal {goal.goal_id} "{goal.title}" successfully deleted'}),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