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

Task List API - Jasmine S. #134

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
51cb40d
Initialize database and create migration files
bunnaery May 3, 2023
58da86a
Create Task model with id, title, description, completed_by columns
bunnaery May 3, 2023
7cefaaf
Run migrate and upgrade after model changes
bunnaery May 3, 2023
5c0a5ce
Register Task blueprint
bunnaery May 3, 2023
18debd5
Add GET route and function get_all_tasks
bunnaery May 3, 2023
5d008bc
Refactor get_all_tasks to return a list of tasks with the correct key…
bunnaery May 3, 2023
6f5b118
Add GET route and function get_one_task
bunnaery May 3, 2023
24b88d3
Add to_dict class method to Task and refactor routes with it
bunnaery May 3, 2023
1864a82
Update task_id attribute to autoincrement
bunnaery May 4, 2023
78869fe
Add POST method to create new task
bunnaery May 4, 2023
50afbaa
Add PUT method to update one task
bunnaery May 4, 2023
68aaafc
Add DELETE method to delete one task
bunnaery May 4, 2023
4d14057
Refactor create_new_task POST method to validate title and description
bunnaery May 4, 2023
6431a71
Remove skip decorators and finish test assertions
bunnaery May 4, 2023
25e60bb
Add query params for GET route
bunnaery Aug 17, 2023
831be22
Add mark_complete and mark_incomplete routes.
bunnaery Aug 17, 2023
155b47d
Add Slack Bot
bunnaery Aug 17, 2023
18d5384
Modify Goal object with title field. Register blueprint.
bunnaery Aug 17, 2023
d104f96
Create POST route for Goal
bunnaery Aug 17, 2023
5d0eec9
Add GET ALL route for Goal
bunnaery Aug 17, 2023
4a17547
Add GET one route for Goal
bunnaery Aug 17, 2023
1f04c31
Add PUT routes for Goal
bunnaery Aug 17, 2023
a77f727
Add DELETE route for Goal
bunnaery Aug 17, 2023
2bfd36c
Confirm 400 error for invalid POST request
bunnaery Aug 17, 2023
ec6fc1b
Update Task and Goal models. Add from_dict method to Task.
bunnaery Aug 17, 2023
3a05c13
Refactor routes into folder
bunnaery Aug 17, 2023
2d41043
Begin nested routes. Create goal_task route file.
bunnaery Aug 17, 2023
c8205f9
Add POST and GET nested routes
bunnaery Aug 17, 2023
84778a5
Add GET tasks for goal and refactor task to_dict to include goal_id
bunnaery Aug 17, 2023
25662a6
Remove unused imports. Move Slack API call back inside PATCH.
bunnaery Aug 17, 2023
4f44702
Freeze requirements
bunnaery Aug 17, 2023
0d7eaad
Add Render Database URI
bunnaery Aug 17, 2023
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
11 changes: 9 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ def create_app(test_config=None):
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 +31,11 @@ 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)
from .routes.goal_task_routes import goal_task_bp
app.register_blueprint(goal_task_bp)
Comment on lines +34 to +39

Choose a reason for hiding this comment

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

Excellent!


return app
9 changes: 9 additions & 0 deletions app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,12 @@

class Goal(db.Model):

Choose a reason for hiding this comment

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

Excellent!

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

def to_dict(self):

Choose a reason for hiding this comment

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

Yay, helper function!

goal = {
"id": self.goal_id,
"title": self.title
}
return goal
26 changes: 25 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,28 @@


class Task(db.Model):

Choose a reason for hiding this comment

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

Excellent!

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.Text)
completed_at = db.Column(db.DateTime, nullable=True, default=None)
goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'), nullable=True)
goal = db.relationship("Goal", back_populates="tasks")

def to_dict(self):
task = {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": False,
}
if self.completed_at is not None:
task["is_complete"] = True
if self.goal_id is not None:
task["goal_id"] = self.goal_id
return task

@classmethod
def from_dict(cls, task_data):
new_task = Task(title=task_data["title"],
description=task_data["description"])
return new_task
Comment on lines +12 to +29

Choose a reason for hiding this comment

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

Yay, helper functions!

1 change: 0 additions & 1 deletion app/routes.py

This file was deleted.

Empty file added app/routes/__init__.py
Empty file.
69 changes: 69 additions & 0 deletions app/routes/goal_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from flask import Blueprint, jsonify, request
from sqlalchemy import desc, asc
from app import db
from app.models.goal import Goal


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


@goal_bp.route("", methods=["POST"])
def create_new_goal():

Choose a reason for hiding this comment

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

Excellent!

request_body = request.get_json()
try:
new_goal = Goal(
title=request_body["title"],
)
except KeyError:
return {"details": "Invalid data"}, 400

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

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


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

Choose a reason for hiding this comment

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

Excellent!

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

response = []
for goal in goals:
response.append(goal.to_dict())

return jsonify(response), 200


@goal_bp.route("/<goal_id>", methods=["GET"])
def get_one_goal(goal_id):

Choose a reason for hiding this comment

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

Excellent!

goal = Goal.query.get_or_404(goal_id)
return {"goal": goal.to_dict()}, 200


@goal_bp.route("/<goal_id>", methods=["PUT"])
def update_one_goal(goal_id):

Choose a reason for hiding this comment

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

Excellent!

goal = Goal.query.get_or_404(goal_id)
request_body = request.get_json()

goal.title = request_body["title"]

db.session.commit()

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


@goal_bp.route("/<goal_id>", methods=["DELETE"])
def delete_one_goal(goal_id):

Choose a reason for hiding this comment

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

Excellent!

goal = Goal.query.get_or_404(goal_id)

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

return {"details": (f'Goal {goal.goal_id} "{goal.title}" '
'successfully deleted')}, 200
36 changes: 36 additions & 0 deletions app/routes/goal_task_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from flask import Blueprint, jsonify, request
from app import db
from app.models.goal import Goal
from app.models.task import Task


goal_task_bp = Blueprint("goal_task", __name__, url_prefix="/goals")


@goal_task_bp.route("/<goal_id>/tasks", methods=["POST"])
def post_task_ids_to_goal(goal_id):

Choose a reason for hiding this comment

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

Excellent!

request_body = request.get_json()
goal = Goal.query.get_or_404(goal_id)

for id in request_body["task_ids"]:
task = Task.query.get_or_404(id)
task.goal = goal

db.session.commit()

return {"id": int(goal_id), "task_ids": request_body["task_ids"]}, 200


@goal_task_bp.route("/<goal_id>/tasks", methods=["GET"])
def get_tasks_for_goal(goal_id):

Choose a reason for hiding this comment

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

Great job!

request_body = request.get_json()
goal = Goal.query.get_or_404(goal_id)

response = goal.to_dict()
response["tasks"] = []
for task in goal.tasks:
response["tasks"].append(task.to_dict())

db.session.commit()

Choose a reason for hiding this comment

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

Since this is a GET request and we are not making any changes to the goal or the tasks, there is no need to commit anything to the db.


return jsonify(response), 200
99 changes: 99 additions & 0 deletions app/routes/task_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from flask import Blueprint, jsonify, request
from sqlalchemy import desc, asc
from app import db
from app.models.task import Task
from datetime import datetime, timezone
import requests
import os


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


@task_bp.route("", methods=["GET"])
def get_tasks():

Choose a reason for hiding this comment

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

Excellent!

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

response = []
for task in tasks:
response.append(task.to_dict())

return jsonify(response), 200


@task_bp.route("/<task_id>", methods=["GET"])
def get_task(task_id):

Choose a reason for hiding this comment

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

Excellent!

task = Task.query.get_or_404(task_id)
return {"task": task.to_dict()}, 200


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

Choose a reason for hiding this comment

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

Excellent!

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

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

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


@task_bp.route("/<task_id>", methods=["PUT"])
def update_task(task_id):

Choose a reason for hiding this comment

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

Excellent!

task = Task.query.get_or_404(task_id)
request_body = request.get_json()

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

db.session.commit()

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


@task_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def mark_complete(task_id):

Choose a reason for hiding this comment

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

Excellent!

task = Task.query.get_or_404(task_id)

task.completed_at = str(datetime.now(timezone.utc))

slack_bot = requests.post('https://slack.com/api/chat.postMessage',
data={
'channel': 'C05N61M2JHG',
'text': f'Task "{task.title}" was marked complete!'},
headers={'Authorization': os.environ.get("SLACK_API_TOKEN")})

db.session.commit()

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


@task_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def mark_incomplete(task_id):

Choose a reason for hiding this comment

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

Excellent!

task = Task.query.get_or_404(task_id)

task.completed_at = None

db.session.commit()

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


@task_bp.route("/<task_id>", methods=["DELETE"])
def delete_task(task_id):

Choose a reason for hiding this comment

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

Excellent!

task = Task.query.get_or_404(task_id)

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

return {"details": (f'Task {task.task_id} "{task.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