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

Amethyst - Hannah Watkins #132

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ee10423
changed 3 files to create task model
Hannah1Watkins May 14, 2023
fcbfa13
updated routes.py
Hannah1Watkins May 14, 2023
4553537
Changed task model
Hannah1Watkins May 15, 2023
eb33fc2
Created model for wave 1 again
Hannah1Watkins May 16, 2023
f6af1c4
created POST method to create task
Hannah1Watkins May 16, 2023
6bb5571
created functions to get specific task & update a task
Hannah1Watkins May 16, 2023
8fc3155
created DELETE method to delete a task
Hannah1Watkins May 16, 2023
b0a3327
Updated delete_task function to also return 404 if task not found
Hannah1Watkins May 16, 2023
f843d3d
created get task not found func - completed wave 1
Hannah1Watkins May 16, 2023
3a390dc
Updated get_all_tasks to complete wave 2
Hannah1Watkins May 16, 2023
69a0bca
completed wave 3 - created mark_complete/incomplete functions
Hannah1Watkins May 16, 2023
323aa4a
Modified mark_complete func to call slack api & created func to send …
Hannah1Watkins May 17, 2023
4070498
fixed typo on slack api url
Hannah1Watkins May 17, 2023
82e5587
Completed wave 5 tests- registerd bp
Hannah1Watkins May 18, 2023
c6302f7
created POST route t get specific goal, corrected goal.py file imports
Hannah1Watkins May 18, 2023
520f562
corrected goal.py id, created routes to get specific goal
Hannah1Watkins May 18, 2023
877bebc
created route to delete goal
Hannah1Watkins May 18, 2023
424ac44
updated get_goal to account for 404 test
Hannah1Watkins May 18, 2023
1e56dd3
updated delete_goal func to account for 404, corrected wave 5 test/ p…
Hannah1Watkins May 18, 2023
b3f7133
uncommented tests, created one to many relationship between tasks & g…
Hannah1Watkins May 18, 2023
fab2dc0
Completed wave 6 tests
Hannah1Watkins May 18, 2023
47c2dc6
created route to get task for a specific goal
Hannah1Watkins May 18, 2023
b208f6f
created endpoint to get tasks for specific goal
Hannah1Watkins May 18, 2023
c2ac9d3
Corrected GET method
Hannah1Watkins May 24, 2023
c55a1d6
Passing all but last test in wave 6
Hannah1Watkins May 24, 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
7 changes: 6 additions & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def create_app(test_config=None):
app.config["TESTING"] = True
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_TEST_DATABASE_URI")
app.config["SLACK_TOKEN"] = os.environ.get("SLACK_TOKEN")


# Import models here for Alembic setup
from app.models.task import Task
Expand All @@ -30,5 +32,8 @@ def create_app(test_config=None):
migrate.init_app(app, db)

# Register Blueprints here

from app.routes import tasks_bp
from app.routes import goals_bp
app.register_blueprint(tasks_bp)
app.register_blueprint(goals_bp)
Comment on lines +35 to +38

Choose a reason for hiding this comment

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

Great work registering these blueprints ✅

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


class Goal(db.Model):

Choose a reason for hiding this comment

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

Great job completing Task List Hannah! Your code looks clean and easily readable. I want to point out the good variable naming and your code is DRY. Great use of helper methods in your models! Excellent work using blue prints & creating RESTful CRUD routes for each model.

goal_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(56))
tasks = db.relationship('Task', backref='goal', lazy=True)

def to_dict(self):
return{
"id": self.goal_id,
"title": self.title,
}
Comment on lines +8 to +12

Choose a reason for hiding this comment

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

Great job creating this reusable helper function

19 changes: 18 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
from app import db


# create task model, define 'Task' using SQLAlchemy
class Task(db.Model):
task_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(56))
description = db.Column(db.String(200))
completed_at = db.Column(db.DateTime, nullable=True)
goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True)
goal = db.relationship("Goal", back)
Comment on lines +9 to +10

Choose a reason for hiding this comment

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

Nice approach creating this relationship to your Goal model.

def to_dict(self):
task_dict = {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": True if self.completed_at else False
}

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

return task_dict
307 changes: 306 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1 +1,306 @@
from flask import Blueprint
from flask import Blueprint, jsonify, request

Choose a reason for hiding this comment

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

👍🏾 These imports help a great deal when it comes to our request & response cycle

from app import db
from app.models.task import Task
from app.models.goal import Goal
from datetime import datetime
import requests

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

# create route (get) to get all tasks
@tasks_bp.route("", methods=["GET"])
def get_all_tasks():
tasks_response = []
sort = request.args.get("sort")

if sort == "asc":
tasks = Task.query.order_by(Task.title.asc()).all()
elif sort == "desc":
tasks = Task.query.order_by(Task.title.desc()).all()
else:
Comment on lines +17 to +21

Choose a reason for hiding this comment

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

Great job with this query param logic

tasks = Task.query.all()

for task in tasks:
tasks_response.append({
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": task.completed_at != None
})

return jsonify(tasks_response)


# create a task
@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 jsonify({"details": "Invalid data"}), 400

new_task = Task(
title = request_body.get("title"),
description = request_body.get("description"),
completed_at = request_body.get("completed_at")
)

db.session.add(new_task)

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 follow:

  • 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
    • deleting a record.
  • By referencing db.session.add() you are able to use the SQLAlchemy method to store a new record of the task model

db.session.commit()

return jsonify({
"task": {
"id": new_task.task_id,
"title": new_task.title,
"description": new_task.description,
"is_complete": new_task.completed_at != None
}
}), 201

Choose a reason for hiding this comment

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

Usually a resource creation relates to a 201 response code 👍🏾



# get a specific task
@tasks_bp.route("/<task_id>", methods=["GET"])
def get_specific_task(task_id):
task = Task.query.get_or_404(task_id)

return jsonify({
"task": {
"id": task.task_id,
"title": task.title,
"description": task.desciption,
"is_complete": task.completed_at != None
}
})



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

if task is None:
return jsonify({"details": f"Task {task_id} was not found."}), 404

task.title = request_body.get("title", task.title)
task.description = request_body.get("description", task.description)

db.session.commit()

return jsonify({
"task": {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": task.completed_at != None
}
})


# detele a task & return 404 + message if not found
@tasks_bp.route("/<task_id>", methods=["DELETE"])
def delete_task(task_id):
task = Task.query.get(task_id)

if task is None:
return jsonify({"details": f"Task {task_id} was not found."}), 404

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

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


# Get task not found
@tasks_bp.route("/<int:task_id>", methods=["GET"])
def get_task_not_found(task_id):
task = Task.query.get(task_id)

if task is None:
return jsonify({"details": f"Task {task_id} was not found."}), 404

return jsonify({
"task": {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": task.completed_at != None
}
})

# mark task complete
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def mark_complete(task_id):
task = Task.query.get(task_id)

if task is None:
return jsonify({"details": f"Task {task_id} was not found."}), 404

task.completed_at = datetime.now()
db.session.add(task)
db.session.commit()

message = f"Task {task.title} is complete."
send_slack_request("study-sesh", message)

return jsonify({
"task": {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": True
}
})


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

if task is None:
return jsonify({"details": f"Task {task_id} was not found."}), 404

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

return jsonify({
"task": {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": False
}
})


# create function that sends request to slack API
def send_slack_request(channel, message):
path = "https://slack.com/api/chat.postMessage"
headers = {
"Type": "application/json",
"Authorization": "xoxb-4715007748918-5273625376965-Fd54AOr5GENxXWt10KbNsgpE"
}
payload = {
"channel": channel,
"text": message
}
response = requests.post(path, headers=headers, json=payload)
response.raise_for_status()


# get a goal
@goals_bp.route("", methods=["GET"])
def get_goals():
goals = Goal.query.all()
goals_dict = [goal.to_dict() for goal in goals]
return jsonify(goals_dict)


# create a new goal
@goals_bp.route("", methods=["POST"])
def create_goal():
request_data = request.get_json()
if "title" not in request_data:
return jsonify({"details": "Invalid data"}), 400

title = request_data["title"]
goal = Goal(title=title)
db.session.add(goal)
db.session.commit()

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


# update existing goal
@goals_bp.route("/<goal_id>", methods=["PUT"])
def update_goal(goal_id):
goal = Goal.query.get(goal_id)

if goal is None:
return jsonify({"details": f"Goal {goal_id} was not found."}), 404

request_data = request.get_json()
goal.title = request_data.get("title", goal.title)
db.session.commit()

return jsonify({"goal": goal.to_dict()})


# get specific goal/ account for 404
@goals_bp.route("/<goal_id>", methods=["GET"])
def get_goal(goal_id):
goal = Goal.query.get(goal_id)

if goal is None:
return jsonify({"details": f"Goal {goal_id} was not found."}), 404

return jsonify({"goal": goal.to_dict()})


# delete a goal
@goals_bp.route("/<goal_id>", methods=["DELETE"])
def delete_goal(goal_id):
goal = Goal.query.get(goal_id)

if goal is None:
return jsonify({"details": f"Goal {goal_id} was not found."}), 404

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

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


# Add a task to a goal
@goals_bp.route("/<goal_id>/tasks", methods=["POST"])
def goal_tasks_post(goal_id):
goal = Goal.query.get(goal_id)

if not goal:
return {"message": f"Goal {goal_id} not found"}, 404

request_body = request.get_json()

answer = {
"id": goal.goal_id,
"task_ids": request_body["task_ids"],
}

for task_id in request_body["task_ids"]:
task = Task.query.get(task_id)
if not task:
return {
"message":
f"Task {task_id} not found"
}, 404

goal.tasks.append(task)

db.session.commit()

return answer, 200

# get task for specific goal
@goals_bp.route("/<goal_id>/tasks", methods=["GET"])
def get_task_for_goal(goal_id):
goal = Goal.query.get(goal_id)
if not goal:
return {"message": f"Goal not found"}, 404

answer = {
"id": goal.goal_id,
"title": goal.title,
"tasks": [],
}

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

return answer, 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.
Loading