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

Finished Waves 1-6 #118

Open
wants to merge 4 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
4 changes: 4 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,9 @@ def create_app(test_config=None):
migrate.init_app(app, db)

# Register Blueprints here
from .routes.task_routes import task_bp
app.register_blueprint(task_bp)
from .routes.goal_routes import goal_bp
app.register_blueprint(goal_bp)

return app
34 changes: 33 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
from app import db

from flask import jsonify, abort, make_response

class Goal(db.Model):

Choose a reason for hiding this comment

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

👍


goal_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, nullable = False)
tasks = db.relationship("Task", back_populates="goal", lazy=True)

def to_json(self):

return {
"id": self.goal_id,
"title": self.title
}

@classmethod
def validate(cls, goal_id):
try:
goal_id = int(goal_id)
except:
abort(make_response(jsonify(f"{goal_id} is not a valid goal id."),400))
goal = cls.query.get(goal_id)
if goal:
return goal
abort(make_response(jsonify(f"Goal with id of {goal_id} was not found"),404))

@classmethod
def create(cls,request_body):
new_goal = cls(
title = request_body['title']

)
return new_goal
Comment on lines +30 to +34

Choose a reason for hiding this comment

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

I think line 31 and the paren on line 33 needs to be tabbed over to the right one more time to match the opening bracket on line 30.

You can also return the class without instantiating it and saving it to a variable like:

return cls(
    title = request_body['title']
)


def update(self,request_body):
self.title = request_body["title"]
53 changes: 52 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,56 @@
from app import db

from flask import jsonify, abort, make_response

class Task(db.Model):

Choose a reason for hiding this comment

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

Nicely done


task_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, nullable = False)
description = db.Column(db.String)
completed_at = db.Column(db.DateTime)
goal_id = db.Column (db.Integer, db.ForeignKey('goal.goal_id'), nullable=True)
goal = db.relationship("Goal", back_populates="tasks")

def to_json(self):
if not self.completed_at:
is_complete = False
else:
is_complete = True
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.

Another way you could write this is within the task_dict after line 23, like:

"is_complete": True if self.completed_at else False


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

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

return task_dict



@classmethod
def validate(cls, task_id):
try:
task_id = int(task_id)
except:
abort(make_response(jsonify(f"{task_id} is not a valid task id."),400))
task = cls.query.get(task_id)
if task:
return task
abort(make_response(jsonify(f"Task with id of {task_id} was not found"),404))

@classmethod
def create(cls,request_body):
new_task = cls(
title = request_body['title'],
description = request_body['description'],
completed_at = request_body.get('completed_at', None)

)
Comment on lines +46 to +51

Choose a reason for hiding this comment

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

Lines 47 -51 needs to be indented one level deeper.

return new_task

def update(self,request_body):
self.title = request_body["title"]
self.description = request_body["description"]
1 change: 0 additions & 1 deletion app/routes.py

This file was deleted.

113 changes: 113 additions & 0 deletions app/routes/goal_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from datetime import date
from urllib import response

Choose a reason for hiding this comment

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

You didn't actually end up using/needing this module so you can delete line 2 here

from flask import Blueprint, jsonify, make_response, abort, request
from app.models.goal import Goal
from app.models.task import Task
from app import db

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

@goal_bp.route("", methods = ["GET"])
def get_all_goal():
goal_response_body = []

if request.args.get("sort") == "asc":
goals = Goal.query.order_by(Goal.title.asc())
elif request.args.get("sort") == "desc":
goals = Goal.query.order_by(Goal.title.desc())
else:
goals = Goal.query.all()

for goal in goals:
goal_response_body.append(goal.to_json())
Comment on lines +21 to +22

Choose a reason for hiding this comment

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

This works well and is readable, but if you'd like to incorporate list comprehensions into your code, you could write it like this:

goals_response = [goal.to_json() for goal in goals]


return jsonify(goal_response_body), 200

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

Choose a reason for hiding this comment

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

Don't forget to remove debugging print statements when you're done with them.


try:
new_goal = Goal.create(request_body)
except KeyError:
return {"details": "Invalid data"}, 400
Comment on lines +31 to +34

Choose a reason for hiding this comment

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

Nice error handling here


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

response_body = {}
response_body["goal"] = new_goal.to_json()
return jsonify(response_body), 201
Comment on lines +39 to +41

Choose a reason for hiding this comment

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

You can also create a dictionary in line on line 41 and directly return it like this (then you could delete lines 39-40):

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


@goal_bp.route("/<id>", methods = ["GET"])
def get_one_goal(id):
one_goal = Goal.validate(id)
response_body = {}
response_body["goal"] = one_goal.to_json()

return jsonify(response_body), 200
Comment on lines +46 to +49

Choose a reason for hiding this comment

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

Here, too, you can also return the response dictionary in line on line 49 (then delete lines 46-47)

return jsonify({"goal": one_goal.to_json()}), 200


@goal_bp.route("/<id>", methods = ["PUT"])
def update_goal(id):
one_goal = Goal.validate(id)
request_body = request.get_json()

one_goal.update(request_body)

Choose a reason for hiding this comment

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

The update() instance method for the Goal class is pretty simple since it only updates the title, but since we can't assume that the client will always pass us a correctly formatted request we should add some error handling for this method.

For PUT and POST methods, we usually need data validation for the req body being sent in.

Like you did in your POST req, you could add a try/except block around line 56 and if the request body does not have "title" then you could handle a KeyError like you did on line 34 and send back:

return {"details": "Invalid data"}, 400


db.session.commit()
response_body = {}
response_body["goal"] = one_goal.to_json()

return jsonify(response_body), 200


@goal_bp.route("/<id>", methods = ["DELETE"])
def delete_goal(id):
one_goal = Goal.validate(id)
db.session.delete(one_goal)
db.session.commit()

return jsonify({"details": f'Goal {one_goal.goal_id} "{one_goal.title}" successfully deleted'})

Choose a reason for hiding this comment

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

Nice work consistently using the jsonify() method throughout your routes.


@goal_bp.route("/<id>/tasks", methods = ["POST"])
def list_of_task_to_goal(id):

Choose a reason for hiding this comment

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

You could rename this method to something like assign_tasks_to_goal so that it's a bit more descriptive

valid_goal = Goal.validate(id)
request_body = request.get_json()

for task_id in request_body["task_ids"]:
Task.validate(task_id)
task = Task.query.get(task_id)
task.goal_id = valid_goal.goal_id

db.session.commit()
task_list = []

for task in valid_goal.tasks:
task_list.append(task.task_id)

response_body = {}
response_body = {
"id": valid_goal.goal_id,
"task_ids": task_list}
Comment on lines +84 to +92

Choose a reason for hiding this comment

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

It looks like you've created task_list and then add the task ids into it in order to build up the response_body that you return.

You can get the list of task_ids from the request_body variable that you created on line 76.

So if you deleted lines 84-92, you could achieve the same thing by doing this:

return jsonify({"id": valid_goal.goal_id, "task_ids": request_body["task_ids"]}), 200


return jsonify(response_body), 200

@goal_bp.route("/<id>/tasks", methods = ["GET"])
def get_task_one_goal(id):
valid_goal = Goal.validate(id)
task_list = []

for task in valid_goal.tasks:
task_list.append(task.to_json())

response_body = {}
response_body = {
"id": valid_goal.goal_id,
"title": valid_goal.title,
"tasks": task_list}
Comment on lines +106 to +108

Choose a reason for hiding this comment

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

You do have part of this code in your to_json method for the Goal class so you could do something like (and remove lines 104-108:

response_body = goal.to_json()
response_body["tasks"] = tasks
return jsonify(goal_response), 200


return jsonify(response_body), 200



104 changes: 104 additions & 0 deletions app/routes/task_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from datetime import date
from urllib import response

Choose a reason for hiding this comment

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

Delete unused imports

from flask import Blueprint, jsonify, make_response, abort, request
from app.models.task import Task
from app import db


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


@task_bp.route("", methods=["GET"])
def get_all_tasks():
task_response_body = []
# tasks = Task.query.all()

Choose a reason for hiding this comment

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

Remove unused code so it doesn't accidentally get uncommented and execute when we don't expect it to.


if request.args.get("sort") == "asc":
tasks = Task.query.order_by(Task.title.asc())
elif request.args.get("sort") == "desc":
tasks = Task.query.order_by(Task.title.desc())
else:
tasks = Task.query.all()

for task in tasks:
task_response_body.append(task.to_json())

return jsonify(task_response_body), 200

@task_bp.route("", methods=["POST"])
def create_task():
request_body = request.get_json()
print(request_body)

Choose a reason for hiding this comment

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

Remove debugging print statements when you're done with them


try:
new_task = Task.create(request_body)
except KeyError:
return {"details": "Invalid data"}, 400

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

response_body = {}
response_body["task"] = new_task.to_json()
return jsonify(response_body), 201



@task_bp.route("/<id>", methods = ["GET"])
def get_one_tasks(id):
one_task = Task.validate(id)
response_body = {}
response_body["task"] = one_task.to_json()

return jsonify(response_body), 200
Comment on lines +49 to +53

Choose a reason for hiding this comment

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

To make this a 2 line method, you could write the code like this too:

one_task = Task.validate(id)
return jsonify({"task": one_task.to_json()}), 200


@task_bp.route("/<id>", methods = ["PUT"])
def update_task(id):
one_task = Task.validate(id)
request_body = request.get_json()

one_task.update(request_body)

Choose a reason for hiding this comment

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

We should add error handling here in case the client sends an incorrect request body so that we can handle a potential KeyError, like you did in your POST request on lines 33-36


db.session.commit()
response_body = {}
response_body["task"] = one_task.to_json()

return jsonify(response_body), 200

@task_bp.route("/<id>", methods = ["DELETE"])
def delete_task(id):
one_task = Task.validate(id)
db.session.delete(one_task)
db.session.commit()

return jsonify({"details": f'Task {one_task.task_id} "{one_task.title}" successfully deleted'})


@task_bp.route("/<id>/mark_complete", methods = ["PATCH"])
def mark_complete(id):
one_task = Task.validate(id)

one_task.completed_at = date.today()

db.session.commit()
response_body = {}
response_body["task"] = one_task.to_json()

return jsonify(response_body), 200

@task_bp.route("/<id>/mark_incomplete", methods = ["PATCH"])
def mark_incomplete(id):
one_task = Task.validate(id)
one_task.completed_at = None

db.session.commit()
response_body = {}
response_body["task"] = one_task.to_json()

return jsonify(response_body), 200






Empty file added code
Empty file.
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