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

Scissors - Christian C15 #60

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
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
7 changes: 7 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,17 @@ def create_app(test_config=None):
# Import models here for Alembic setup
from app.models.task import Task
from app.models.goal import Goal
from app.routes import task_bp
from .routes import goal_bp


db.init_app(app)
migrate.init_app(app, db)

# Register Blueprints here
app.register_blueprint(task_bp)
app.register_blueprint(goal_bp)

return app


28 changes: 27 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,30 @@


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, nullable=False)
tasks = db.relationship('Task', backref= 'goal', lazy=True)


def now_json(self):
return {
"id": self.goal_id,
"title": self.title
}

def tasks_json(self):
result = []
for task in self.tasks:
task = task.to_json()

Choose a reason for hiding this comment

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

Great use of method to_json within this method.

task["goal_id"] = self.goal_id
result.append(task)
return result


def full_json(self):
return {
"id": self.goal_id,
"title": self.title,
"tasks": self.tasks_json()
}

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


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 to_json(self):

if self.completed_at == None:
is_complete = False
else:
is_complete = True
if self.goal_id:
return {
"id": self.task_id,
"goal_id": self.goal_id,
"title": self.title,
"description": self.description,
"is_complete": is_complete
}
return {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": is_complete
}
Comment on lines +19 to +32

Choose a reason for hiding this comment

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

Notice there is repeated code here. Consider refactoring by creating a variable to hold the dictionary such as

json = 
{"id": self.task_id,           
 "title": self.title,            
"description": self.description,           
"is_complete": is_complete  }

and then conditionally adding the goal info

if self.goal_id:
    json["goal_id"] = self.goal_id

and then return json


188 changes: 187 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,188 @@
from flask import Blueprint
from app import db
from app.models.task import Task
from flask import Blueprint, request, make_response, jsonify
from datetime import datetime
import requests
import flask_migrate
import os
from app.models.goal import Goal



task_bp = Blueprint("task", __name__, url_prefix='/tasks')
goal_bp = Blueprint("goal", __name__, url_prefix='/goals')

@task_bp.route("", methods=["POST"], strict_slashes = False)
def post_task():
request_body = request.get_json()

if "title" in request_body and "description" in request_body and "completed_at" in request_body:
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_json()}), 201
else:
return make_response({"details": "Invalid data"}), 400
Comment on lines +19 to +26

Choose a reason for hiding this comment

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

Consider refactoring to handle the 400 response first. This pattern can increase readability.

Suggested change
if "title" in request_body and "description" in request_body and "completed_at" in request_body:
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_json()}), 201
else:
return make_response({"details": "Invalid data"}), 400
if "title" not in request_body or "description" not in request_body or "completed_at" not in request_body:
return make_response({"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_json()}), 201



@task_bp.route("", methods=["GET"], strict_slashes = False)
def get_all_tasks():
sort_method = request.args.get("sort")
if sort_method == "asc":
tasks = Task.query.order_by(Task.title.asc())
elif sort_method == "desc":
tasks = Task.query.order_by(Task.title.desc())
else:
tasks = Task.query.all()
task_response_body = []
for task in tasks:
task_response_body.append(task.to_json())
return jsonify(task_response_body), 200

@task_bp.route('/<task_id>', methods=['GET'], strict_slashes = False)
def get_single_task(task_id): # same name as parameter route
task = Task.query.get(task_id)
if not task:
return "", 404
return make_response({"task": task.to_json()}), 200
Comment on lines +46 to +48

Choose a reason for hiding this comment

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

This is the pattern I was referring to above. First handle the 400 level response, and then get on with the rest of the program. Nice work!



@task_bp.route("/<task_id>", methods=['DELETE', 'PUT'], strict_slashes = False)
def delete_or_put_tasks(task_id):
task = Task.query.get(task_id)
if not task:
return "", 404
elif request.method == 'DELETE':
db.session.delete(task)
db.session.commit()
return jsonify({
"details": f'Task {task_id} "{task.title}" successfully deleted'
}), 200

elif request.method == 'PUT':
request_body = request.get_json()
task.title = request_body["title"]
task.completed_at = request_body["completed_at"]
task.description = request_body["description"]
db.session.commit()
return jsonify({"task": task.to_json()}), 200


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

Choose a reason for hiding this comment

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

Your code has a few different ways of return 400 level errors. To increase readability, try to choose one way and stick with it. For instance, there is this code above return "", 404


if task:

Choose a reason for hiding this comment

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

Since you've already dealt with if task == None, another conditional test is not necessary here.

task.completed_at = datetime.utcnow()
db.session.commit()
call_slack(task)

Choose a reason for hiding this comment

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

Nice use of a helper function!

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



def call_slack(task):
key = os.environ.get("API_KEY")
url = "https://slack.com/api/chat.postMessage"
slack_str = f"Someone just completed the task {task.title}"
requests.post(url, data={"token":key ,"channel":"general" , "text": slack_str})



@task_bp.route('/<task_id>/mark_incomplete', methods=["PATCH"], strict_slashes = False)
def mark_incomplete(task_id):
task = Task.query.get(task_id)
if task == None:
return make_response(), 404

task.completed_at = None
db.session.commit()
return jsonify({"task": task.to_json()}), 200



@goal_bp.route("", methods=["POST"], strict_slashes = False)
def post_new_goal():
request_body = request.get_json()

if "title" in request_body:
new_goal = Goal(title=request_body["title"])
db.session.add(new_goal)
db.session.commit()
return jsonify({"goal": new_goal.now_json()}), 201
else:
return make_response({"details": "Invalid data"}), 400

@goal_bp.route("", methods=["GET"], strict_slashes = False)
def get_all_goals():
sort_method = request.args.get("sort")
if sort_method == "asc":
goals = Goal.query.order_by(Goal.title.asc())
elif sort_method == "desc":
goals = Goal.query.order_by(Goal.title.desc())
else:
goals = Goal.query.all()
goal_response_body = []
for goal in goals:
goal_response_body.append(goal.now_json())
return jsonify(goal_response_body), 200



@goal_bp.route('/<goal_id>', methods=['GET'], strict_slashes = False)
def get_single_goal(goal_id):
goal = Goal.query.get(goal_id)
if not goal:

Choose a reason for hiding this comment

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

if not goal is very python-y. Nice work. I encourage you to favor this way of writing the code as opposed to if goal == None

return "", 404
return make_response({"goal": goal.now_json()}), 200

@goal_bp.route("/<goal_id>", methods=['DELETE', 'PUT'], strict_slashes = False)
def delete_or_put_goals(goal_id):
goal = Goal.query.get(goal_id)
if not goal:
return "", 404
elif request.method == 'DELETE':
db.session.delete(goal)
db.session.commit()
return jsonify({
"details": f'Goal {goal_id} "{goal.title}" successfully deleted'
}), 200

elif request.method == 'PUT':
request_body = request.get_json()
goal.title = request_body["title"]
db.session.commit()
return jsonify({"goal": goal.now_json()}), 200


@goal_bp.route("/<int:goal_id>/tasks", methods=["POST"], strict_slashes = False)
def post_new_task(goal_id):
request_body = request.get_json()
task_ids = request_body["task_ids"]
goal = Goal.query.get(goal_id)
for task_id in task_ids:
task = Task.query.get(task_id)
if task.goal_id != goal_id:

Choose a reason for hiding this comment

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

I'm not sure what this line of code is doing. Let's talk about it in our 1:1. I think these two values will always not be equal because one is an int and the other is a str that comes from the route.

goal.tasks.append(task)
response_body = {
"id": goal_id,
"task_ids": task_ids
}
return jsonify(response_body), 200


@goal_bp.route('/<int:goal_id>/tasks', methods=['GET'])
def get_tasks_goals(goal_id):
goal = Goal.query.get(goal_id)
if not goal:
return '', 404
return jsonify(goal.full_json()), 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