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

Sapphire - Linh H. #127

Open
wants to merge 86 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
43ee5fd
Established tasks_bp blueprint in app routes.py module.
remitly-linh May 8, 2023
fb42416
Imported and regiestered tasks_bp Blueprints in app __init__.py module.
remitly-linh May 8, 2023
702295d
Added Task model and created associated columns for task table within…
remitly-linh May 8, 2023
c6a044d
Generated migrations folder and necessary alembic, env, etc. files to…
remitly-linh May 8, 2023
a122ccf
Wrote to_dict helper function within model task.py module in order to…
remitly-linh May 8, 2023
e651e96
Established all necessary imports in app routes.py module.
remitly-linh May 8, 2023
32f1fb1
Wrote POST endpoint and create_task function in app routes.py module.
remitly-linh May 8, 2023
7750431
Updated create_task funciton to return invalid data and 400 HTTP resp…
remitly-linh May 8, 2023
75f10d9
Wrote GET endpoint and read_all_tasks function in app routes.py module.
remitly-linh May 8, 2023
6f32476
Refactored read_all_tasks function in app routes.py module.
remitly-linh May 8, 2023
8e701a7
Removed 400 response if completed_at is not in the body request.
remitly-linh May 8, 2023
0f1c425
Wrote GET endpoint for single task and read_one_task function in app …
remitly-linh May 8, 2023
099312b
Wrote validate_model function in app routes.py module.
remitly-linh May 8, 2023
caad9c0
Wrote PUT endpoint and update_task function in app routes.py module.
remitly-linh May 8, 2023
38252f1
Wrote DELETE endpoint and delete_task funciton in app routes.py module.
remitly-linh May 8, 2023
d1d62c1
Fixed indentation and reformatted to_dict helper function in model Ta…
remitly-linh May 8, 2023
b9d3db3
Added missing return statement in validate_model function in app rout…
remitly-linh May 8, 2023
00f641c
Fixed AttributeError by eliminating unecessary parameter in endpoints…
remitly-linh May 8, 2023
c3789b2
Added completed_as attribute in to_dict helper function within model …
remitly-linh May 8, 2023
cf6e15d
Changed response body in POST endpoint to return completed_at instead…
remitly-linh May 8, 2023
8b7c0a1
Completed test_get_task_not_found assertion in wave_01 module.
remitly-linh May 8, 2023
426efd3
Completed test_update_task_not_found assertion in wave_01 module.
remitly-linh May 8, 2023
701210a
Completed test_delete_task_not_found in wave_01 module.
remitly-linh May 8, 2023
affd1ef
Wrote @classmethod from_dict helper function in app method task.py mo…
remitly-linh May 10, 2023
01515da
Refactored create_task function (within the app routes.py modeul) to …
remitly-linh May 10, 2023
8a53918
Updated return statement in read_one_task function (within the app ro…
remitly-linh May 10, 2023
a92765c
Imported asc, desc features from sqlalchemy in app routes.py module.
remitly-linh May 10, 2023
5a22ed3
Added sorting capability to read_all_tasks function in app routes.py.
remitly-linh May 10, 2023
b99a25e
Update class Task and to_dict helper function to account for task com…
remitly-linh May 11, 2023
ae01536
Wrote PATCH endpoint and mark_task_incomplete function in app routes.…
remitly-linh May 11, 2023
f369be7
Imported datetime in app routes.py module.
remitly-linh May 11, 2023
104044c
Wrote PATCH endpoint and mark_task_complete function in app routes.py…
remitly-linh May 11, 2023
6047864
Completed assertion for test_mark_complete_missing_task function in t…
remitly-linh May 11, 2023
c996f27
Completed assertion for test_mark_incomplete_missing_task function in…
remitly-linh May 11, 2023
29ad072
Imported os, requests in app routes.py module.
remitly-linh May 11, 2023
3658a19
Wrote slack_bot_message helper function in app routes.py module.
remitly-linh May 11, 2023
1a1390a
Updated mark_task_complete function to call Slack API and display Sla…
remitly-linh May 11, 2023
e244465
Added title attribute to class Goal in app model goal.py module.
remitly-linh May 11, 2023
57b686b
Establish bidirectional relationship in goal.py module to task databa…
remitly-linh May 11, 2023
6b8422f
Establish bidirectional relationship in task.py module to goal databa…
remitly-linh May 11, 2023
bb94cd1
Linked goal and task db by adding goal_id column as foreign key to ta…
remitly-linh May 11, 2023
7a323ea
Wrote to_dict helper function within class Goal of app models goal.py…
remitly-linh May 11, 2023
24b4b16
Changed registered blueprint source from .routes to app.models.task f…
remitly-linh May 11, 2023
baf6450
Registered blueprints for goals_bp in app dunder init module.
remitly-linh May 11, 2023
5ba683b
Wrote classmethod from_dict helper function in app models goal.py mod…
remitly-linh May 11, 2023
1ffd7fa
Renamed routes.py module in app folder to task_routes.py.
remitly-linh May 11, 2023
3b0c98b
Created new module in app folder named goal_routes.py.
remitly-linh May 11, 2023
eb2ca32
Fixed pathway typos in registered task and goal blueprints within the…
remitly-linh May 11, 2023
378dad6
Implemented all necessary imports in app goal_routes.py module.
remitly-linh May 11, 2023
011b73e
Created goals_bp local variable to hold Blueprint instance route in a…
remitly-linh May 11, 2023
ec1e8cf
Wrote POST endpoint and create_goal function in app goal_routes.py mo…
remitly-linh May 11, 2023
b8d0921
Corrected silly typo in make_response command throughout app goal_rou…
remitly-linh May 11, 2023
463a3a8
Imported asc, desc functionality from sqlalchemy in app goal_routes.p…
remitly-linh May 11, 2023
614f249
Wrote GET endpoint for read_all_goals function in app goal_routes.py …
remitly-linh May 11, 2023
b5c1aea
Removed validate_model and slack_bot_message functions from task_rout…
remitly-linh May 11, 2023
951ce91
Removed routes.py module from app folder.
remitly-linh May 11, 2023
a2ab691
Imported helper_functions into task_routes.py and goal_routes.py modu…
remitly-linh May 11, 2023
0a8d0c3
Moved all necessary imports from the task_routes.py module to the hel…
remitly-linh May 11, 2023
f927beb
Wrote PUT endpoint and update_goal function in app goal_routes.py mod…
remitly-linh May 11, 2023
096f9ab
Wrote DELETE endpoint and delete_goal funciton in the app goal_routes…
remitly-linh May 11, 2023
a34eab2
Refactored validate_model to accomodate any model in helper_functions…
remitly-linh May 11, 2023
e404149
Add cls parameters to validate_model helper funciton and all instance…
remitly-linh May 11, 2023
af29edb
Added cls (Goal) parameters to all instances in which the validate_mo…
remitly-linh May 11, 2023
4491c07
Removed unnecessary app (implied) pathway under registered blueprints…
remitly-linh May 11, 2023
b328ed8
Fixed indentation in app model goal.py module.
remitly-linh May 11, 2023
3a8b9b3
Removed slack_bot_message from imports in goal_routes.py module.
remitly-linh May 11, 2023
525863f
Corrected typo in pathway route for GET (read_one_goal) endpoint.
remitly-linh May 11, 2023
5d6068e
Completed necessary assertions for the test_get_goal_not_found within…
remitly-linh May 11, 2023
e0a3f39
Completed act and assertion statements for test_update_goal function …
remitly-linh May 11, 2023
8141fb7
Completed Act and Assert portion for test_update_goal_not_found funct…
remitly-linh May 13, 2023
2521be9
Completed assertion for test_delete_goal function in test_wave_05.py …
remitly-linh May 13, 2023
ebffe07
Completed Act and Assert portions of test_delete_goal_not_found funci…
remitly-linh May 13, 2023
a158945
Imported Task model/class in goal_routes.py module.
remitly-linh May 13, 2023
fa30d39
Wrote POST endpoint and create_tasks_under_goal function in goal_rout…
remitly-linh May 13, 2023
3f56a5c
Wrote GET endpoint and read_tasks_under_goal function in goal_routes.…
remitly-linh May 13, 2023
6b9a1af
Refactored read_tasks_under_goal function in goal_routes.py module.
remitly-linh May 13, 2023
f7d2a08
Updated function names for better clarification in goal_routes.py mod…
remitly-linh May 13, 2023
ab28522
Added status code to returned response body in read_tasks_under_one_g…
remitly-linh May 13, 2023
2b6f24f
Updated to_dict helper function to account for goal_id in task.py mod…
remitly-linh May 13, 2023
0d263ce
Completed assertion for test_get_tasks_for_specific_goal_no_goal func…
remitly-linh May 13, 2023
494b364
Ensured commited version of test_wave modules all have @pytest.mark.…
remitly-linh May 13, 2023
c2a4485
Added all migrations to project repo.
remitly-linh May 13, 2023
a5972f2
Updated spacing between functions for consistency in goal_routes.py a…
remitly-linh May 13, 2023
c253929
Added ascii art to goal.py module.
remitly-linh May 13, 2023
c27a1bc
Fixed ascii bunny ear in goal.py module.
remitly-linh May 13, 2023
304e025
Connect to Render db.
remitly-linh May 16, 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
9 changes: 7 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ 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 +30,10 @@ def create_app(test_config=None):
migrate.init_app(app, db)

# Register Blueprints here
from .task_routes import tasks_bp
app.register_blueprint(tasks_bp)

from .goal_routes import goals_bp
app.register_blueprint(goals_bp)
Comment on lines +33 to +37

Choose a reason for hiding this comment

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

Beautiful imports in your app init! 🎉


return app
93 changes: 93 additions & 0 deletions app/goal_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from app import db
from app.models.goal import Goal
from app.models.task import Task
from app.helper_functions import validate_model
from flask import Blueprint, jsonify, make_response, request
from sqlalchemy import asc, desc


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


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

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

new_goal = Goal.from_dict(request_body)

db.session.add(new_goal)
db.session.commit()
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.

Great job using db.session.<method-name>. Session gives us access to the following:

  • db is our app’s instance of SQLAlchemy.
  • session represents our active database connection.
  • By referencing db.session we can use SQLAlchemy’s methods to perform tasks like committing a change to a model, storing a new record of a model, and deleting a record.

By referencing db.session.add() you are able to use the SQLAlchemy method to store a new record of the Goal model. ⭐


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


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

task_list = []
for task_id in request_body["task_ids"]:
task = validate_model(Task, task_id)
task.goal = goal
task_list.append(task_id)

db.session.commit()

return make_response({"id": goal.goal_id, "task_ids": task_list}, 200)


@goals_bp.route("", methods=["GET"])
def read_all_goals():
sort_query = request.args.get("sort")
if sort_query:
if sort_query == "asc":
goals = Goal.query.order_by(Goal.title.asc()).all()
elif sort_query == "desc":
goals = Goal.query.order_by(Goal.title.desc()).all()
else:
goals = Goal.query.all()

all_goals = [goal.to_dict() for goal in goals]

return jsonify(all_goals), 200


@goals_bp.route("/<goal_id>", methods=["GET"])
def read_one_goal(goal_id):
goal = validate_model(Goal, goal_id)

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


@goals_bp.route("/<goal_id>/tasks", methods=["GET"])
def read_tasks_under_one_goal(goal_id):
goal = validate_model(Goal, goal_id)

goal_task_list = [task.to_dict() for task in goal.tasks]

Choose a reason for hiding this comment

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

Nice listcomp! 👾


return make_response({"id": goal.goal_id, "title": goal.title, "tasks": goal_task_list}, 200)


@goals_bp.route("/<goal_id>", methods=["PUT"])
def update_goal(goal_id):
goal = validate_model(Goal, goal_id)
request_body = request.get_json()
goal.title = request_body["title"]

db.session.commit()

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


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

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

return make_response({"details":f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"}), 200
29 changes: 29 additions & 0 deletions app/helper_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from app import db

Choose a reason for hiding this comment

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

I love everything about this helper file! Great job pulling out validate_model and slack_bot_message! ❤️

from flask import make_response, abort
import os, requests


def validate_model(cls, model_id):
try:
model_id = int(model_id)
except:
abort(make_response({"message": f"{cls.__name__} {model_id} is invalid"}, 400))

model = cls.query.get(model_id)

if not model:
abort(make_response({"message": f"{cls.__name__} {model_id} not found"}, 404))

return model

def slack_bot_message(message):
slack_api_key = os.environ.get("SLACK_BOT_TOKEN")
slack_url = "https://slack.com/api/chat.postMessage"
header = {"Authorization": slack_api_key}

slack_query_params = {
"channel": "task-notifications",
"text": message
}
print(slack_api_key)
requests.post(url=slack_url, data=slack_query_params, headers=header)
26 changes: 26 additions & 0 deletions app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,29 @@

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

Choose a reason for hiding this comment

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

Beautiful sync to the Task model! Good job remembering to include our lazy parameter!


def to_dict(self):
return dict(
id=self.goal_id,
title=self.title
)

@classmethod
def from_dict(cls, goal_data):
new_goal = Goal(title=goal_data["title"])
return new_goal


# _______________
# | |
# | Thank |
# | you for |
# | reviewing |
# | my code! |
# |_____________|
# ||
# (\__/)||
# (•ㅅ•) ||
# /   >||
Comment on lines +21 to +31

Choose a reason for hiding this comment

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

Wait, this is so cute 😭 TY for showing up!!

41 changes: 40 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,43 @@


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, nullable=False)
description = db.Column(db.String, nullable=False)
completed_at = db.Column(db.DateTime, nullable=True)
is_complete = db.Column(db.Boolean)
goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True)
goal = db.relationship("Goal", back_populates="tasks")
Comment on lines +10 to +11

Choose a reason for hiding this comment

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

Awesome! Beautiful sync up!

Fun fact: the default value for nullable is True so we don't need to explicitly add it 😊


def to_dict(self):
if self.completed_at:
return dict(
id=self.task_id,
title=self.title,
description=self.description,
is_complete=True
)

if self.goal_id and not self.completed_at:
return dict(
id=self.task_id,
goal_id=self.goal_id,
title=self.title,
description=self.description,
is_complete=False
)
else:
return dict(
id=self.task_id,
title=self.title,
description=self.description,
is_complete=False
)
Comment on lines +13 to +36

Choose a reason for hiding this comment

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

This is a great place to DRY our code! Remember that the pipe operator | can merge two dictionaries together, so we could shorten the above to:

def to_dict(self):
    base_goal_dict = {
        "id": self.task_id,
        "title": self.title,
        "description": self.description,
        "is_complete": True
    }

    if self.completed_at:
        return base_goal_dict
    else: 
        base_goal_dict["is_complete"] = False

        if self.goal_id: 
            return base_goal_dict | {"goal_id": self.goal_id, }
        else: 
            return base_goal_dict 


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

Choose a reason for hiding this comment

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

Awesome from_dict instance method!

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

This file was deleted.

92 changes: 92 additions & 0 deletions app/task_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from app import db
from app.models.task import Task
from app.helper_functions import validate_model, slack_bot_message
from flask import Blueprint, jsonify, make_response, request, abort
from sqlalchemy import asc, desc
from datetime import datetime


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


@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:
return make_response({"details": "Invalid data"}, 400)

new_task = Task.from_dict(request_body)

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

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


@tasks_bp.route("", methods=["GET"])
def read_all_tasks():
sort_query = request.args.get("sort")
if sort_query:
if sort_query == "asc":
tasks = Task.query.order_by(Task.title.asc()).all()
elif sort_query == "desc":
tasks = Task.query.order_by(Task.title.desc()).all()
else:
tasks = Task.query.all()

all_tasks = [task.to_dict() for task in tasks]

return jsonify(all_tasks), 200


@tasks_bp.route("/<task_id>", methods=["GET"])
def read_one_task(task_id):
task = validate_model(Task, task_id)

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


@tasks_bp.route("/<task_id>", methods=["PUT"])
def update_task(task_id):
task = validate_model(Task, task_id)
request_body = request.get_json()

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

db.session.commit()

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


@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def mark_task_complete(task_id):
task = validate_model(Task, task_id)

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

slack_bot_message(f"Someone just completed the task {task.title}")

Choose a reason for hiding this comment

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

So clean! So clear! Spectacular! 🌟


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


@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def mark_task_incomplete(task_id):
task = validate_model(Task, task_id)

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

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


@tasks_bp.route("/<task_id>", methods=["DELETE"])
def delete_task(task_id):
task = validate_model(Task, task_id)

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

return make_response({"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