Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rock - Abigail C #51

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
8 changes: 7 additions & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from flask import Flask
from flask import Flask, Blueprint
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
import os
Expand Down Expand Up @@ -30,5 +30,11 @@ 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)
from app.models.goal import Goal

return app
14 changes: 13 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
from flask import current_app
from sqlalchemy.orm import backref
from app import db


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

def to_json_goal(self):
# This method was created so that we do not have to write out the dictionary many times in the routes.py file.
return {
"id": self.goal_id,
"title": self.title,
}
Comment on lines +12 to +17

Choose a reason for hiding this comment

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

👍 nice helper method


35 changes: 34 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
from flask import current_app
from app import db
import datetime


class Task(db.Model):
task_id = db.Column(db.Integer, primary_key=True)
__tablename__ = 'task'
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)
Comment on lines +7 to +12

Choose a reason for hiding this comment

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

👍



def to_json(self):

Choose a reason for hiding this comment

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

👍 glad you put all your logic in the helper method instead of in the route!

if self.completed_at != None:
result = self.completed_at
result = True
elif self.completed_at == None:
result = self.completed_at
result = False

# This method was created so that we do not have to write out the dictionary many times in the routes.py file.
if self.goal_id == None:
return {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": result
}
else:
return {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": result,
"goal_id": self.goal_id

}
Comment on lines +24 to +39

Choose a reason for hiding this comment

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

one thing we could do to shorten this is create the data structure first, then change out the key-value pair you need to.

Suggested change
if self.goal_id == None:
return {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": result
}
else:
return {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": result,
"goal_id": self.goal_id
}
task = {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": result
}
if self.goal_id:
task["goal_id"] = self.goal_id
return task

202 changes: 201 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,202 @@
from flask import Blueprint
from werkzeug.datastructures import Authorization
from app import db
from app.models.task import Task
from flask import Blueprint, request, jsonify, make_response
from datetime import datetime
import os
import requests

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

@tasks_bp.route("/<task_id>", methods=["GET","PUT", "DELETE"])
def get_single_task(task_id):

task = Task.query.get(task_id)
# With the GET, POST and DELETE request if there is nothing we output this
if request == None or task == None:
return jsonify(None), 404
Comment on lines +14 to +17

Choose a reason for hiding this comment

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

👍 this works great! One thing we could do to shorten this is:

Suggested change
task = Task.query.get(task_id)
# With the GET, POST and DELETE request if there is nothing we output this
if request == None or task == None:
return jsonify(None), 404
task = Task.query.get_or_404(task_id)

this will do your 404 check for you!

# This portion is the GET request for only one task
elif request.method == "GET":
return {"task": task.to_json()}, 200
elif request.method == "PUT":
# This portion is the PUT request for only one task
request_body = request.get_json()
task.title = request_body["title"]
task.description = request_body["description"]
db.session.commit()
return {"task": task.to_json()}, 200
elif request.method == "DELETE":
db.session.delete(task)
db.session.commit()
return {
"details": f"Task {task.task_id} \"{task.title}\" successfully deleted"
}, 200

@tasks_bp.route("", methods=["GET"])
def tasks_index():

query_sorted = request.args.get("sort")
if query_sorted == "asc":
# Found in SQLALchemy documentation.
# The order_by method takes the data in the user table (Task)
# and filters by title in ascending order
tasks = Task.query.order_by(Task.title.asc())
elif query_sorted == "desc":
# Found in SQLALchemy documentation.
# The order_by method takes the data in the user table (Task)
# and filters by title in descending order
tasks = Task.query.order_by(Task.title.desc())
else:
tasks = Task.query.all()
# This portion is just a GET request
if tasks == None:
return []
Comment on lines +50 to +53

Choose a reason for hiding this comment

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

good check, but all() will already return an empty list if it doesn't find any tasks! No need for the if statement

else:
tasks_response = []
for task in tasks:
tasks_response.append(task.to_json())
return jsonify(tasks_response), 200


@tasks_bp.route("", methods=["POST"])
def tasks():
try:
# This portion is the POST request
request_body = request.get_json()
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 {"task": new_task.to_json()}, 201
except KeyError:
return {
"details": "Invalid data"}, 400
Comment on lines +74 to +76

Choose a reason for hiding this comment

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

👍 great error checking


@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def mark_complete(task_id):
patch_task = Task.query.get(task_id)
date = datetime.utcnow()
if patch_task == None:
return jsonify(None), 404
Comment on lines +80 to +83

Choose a reason for hiding this comment

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

we could also use the get_or_404() method here, too!

# Mark Complete on an Incompleted Task
patch_task.completed_at = date
bot_notification(patch_task)
db.session.commit()

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


def bot_notification(patch_task):
PATH = "https://slack.com/api/chat.postMessage"
API_TOKEN = os.environ.get("API_KEY")

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

header = {
"Authorization": f"Bearer {API_TOKEN}"

}
result = requests.get(PATH, params=query_params,headers=header)

return result
Comment on lines +92 to +107

Choose a reason for hiding this comment

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

👍 glad you turned this into a helper function!


@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def mark_incomplete(task_id):
patch_task = Task.query.get(task_id)
if patch_task == None:
return jsonify(None), 404
Comment on lines +111 to +113

Choose a reason for hiding this comment

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

here we could also use the get_or_404() method here, too!

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

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


# ============================Goals=========================================

from app.models.goal import Goal
from flask import Blueprint, request, jsonify

goals_bp = Blueprint("goals", __name__, url_prefix="/goals")
Comment on lines +122 to +125

Choose a reason for hiding this comment

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

we could add Goal import to the top. also, we've already imported Blueprint, request, and jsonify, so we don't need to do it again.


@goals_bp.route("",methods=["POST"])
def create_goal():
try:
# This portion is the POST request
request_body = request.get_json()
new_goal = Goal(title=request_body["title"])

db.session.add(new_goal)
db.session.commit()
return {"goal": new_goal.to_json_goal()}, 201
except KeyError:
return {
"details": "Invalid data"}, 400
Comment on lines +129 to +139

Choose a reason for hiding this comment

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

cool! good job experimenting with a try-except statement

@goals_bp.route("", methods=["GET"])
def goals_index():
# This portion is the just a GET request
goals = Goal.query.all()
if goals == None:
return []
Comment on lines +143 to +145

Choose a reason for hiding this comment

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

we can let all() return an empty list for us

else:
goals_response = []
for goal in goals:
goals_response.append(goal.to_json_goal())
return jsonify(goals_response), 200

@goals_bp.route("/<goal_id>", methods=["GET","PUT", "DELETE"])
def get_single_goal(goal_id):

goal = Goal.query.get(goal_id)
# With the GET, POST and DELETE request if there is nothing we output this
if request == None or goal == None:
return jsonify(None), 404
Comment on lines +155 to +158

Choose a reason for hiding this comment

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

we could also use the get_or_404() method here, too!

# This portion is the GET request for only one goal
elif request.method == "GET":
return {"goal": goal.to_json_goal()}, 200
elif request.method == "PUT":
# This portion is the PUT request for only one goal
request_body = request.get_json()
goal.title = request_body["title"]
db.session.commit()
return {"goal": goal.to_json_goal()}, 200
elif request.method == "DELETE":
db.session.delete(goal)
db.session.commit()
return {
"details": f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"
}, 200


# # ===============Establishing One to Many Realtionship=================================


@goals_bp.route("<goal_id>/tasks", methods=["POST"])
def post_tasks_ids_to_goal(goal_id):
goal = Goal.query.get(goal_id)
request_body = request.get_json()

for task_id in request_body["task_ids"]:
task = Task.query.get(task_id)
goal.tasks.append(task)
db.session.commit()
return make_response({"id": goal.goal_id, "task_ids": request_body["task_ids"]}, 200)

@goals_bp.route("<goal_id>/tasks", methods=["GET"])
def getting_tasks_of_one_goal(goal_id):
goal = Goal.query.get(goal_id)
if goal == None:
return jsonify(None), 404
Comment on lines +192 to +194

Choose a reason for hiding this comment

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

we could also use the get_or_404() method here, too!

else:
tasks_from_goal = goal.tasks

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

return {"id":goal.goal_id,"title": goal.title, "tasks": tasks_response}, 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