Skip to content

Amethyst - Elaine W. #116

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

Open
wants to merge 7 commits into
base: main
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
11 changes: 9 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@

def create_app(test_config=None):
app = Flask(__name__)

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")
"RENDER_DATABASE_URI")
else:
app.config["TESTING"] = True
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
Expand All @@ -30,5 +33,9 @@ def create_app(test_config=None):
migrate.init_app(app, db)

# Register Blueprints here

from .routes import task_bp
app.register_blueprint(task_bp)
from .routes import goal_bp
app.register_blueprint(goal_bp)
Comment on lines +36 to +39

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
14 changes: 12 additions & 2 deletions app/models/goal.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
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 Elaine! 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)
goal_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String)
tasks = db.relationship("Task", back_populates="goal", lazy='select')

# @classmethod
# # in class methods, cls must come first. it's a reference to the class itself
# def from_dict(cls, task_data):
# new_goal = goal(
# title=goal_data["title"]
# )

# return new_goal
28 changes: 26 additions & 2 deletions app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
from app import db


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'))
goal = db.relationship('Goal', back_populates='tasks')

Comment on lines +7 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. Why not use lazy here?

@classmethod
def from_dict(cls, task_data):
new_task = Task(title=task_data["title"],
description=task_data["description"],
completed_at=None)
return new_task

def to_dict(self):
task_as_dict = {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": bool(self.completed_at)
}

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

return task_as_dict
Comment on lines +11 to +29

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

281 changes: 280 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1 +1,280 @@
from flask import Blueprint
from flask import Blueprint, jsonify, request, abort, make_response

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 sqlalchemy import desc
from datetime import datetime
import requests
import os

task_bp = Blueprint("tasks", __name__, url_prefix="/tasks")
goal_bp = Blueprint("goals", __name__, url_prefix="/goals")
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.

👍🏾


def validate_model(cls, model_id):
try:
model_id = int(model_id)
except:
abort(make_response({"message":f"{model_id} invalid type ({type(model_id)})"}, 400))
# abort(make_response({"message":f"Invalid data"}, 400))

model = cls.query.get(model_id)

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

return model

Comment on lines +13 to +26

Choose a reason for hiding this comment

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

😁


# Create a Task: Valid Task with 'null' 'completed at'
@task_bp.route("", methods=["POST"])
def handle_tasks():
# handle the HTTP request body - pasrses JSON body into a Python dict
request_body = request.get_json()

# create a new task instance from the request
if len(request_body) < 2:
return {"details": "Invalid data"}, 400
else:
new_task = Task.from_dict(request_body)

# database collects new changes - adding new_task as a record
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

# database saves and commits the new changes
db.session.commit()

return {
"task":
{
"id": new_task.task_id,
"title": new_task.title,
"description": new_task.description,
"is_complete": bool(new_task.completed_at)

Choose a reason for hiding this comment

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

😁

}
}, 201


# Get Saved Tasks from the database
@task_bp.route("", methods=["GET"])
def read_all_tasks():
task_response = []

sort_query = request.args.get("sort")

if sort_query == "asc":
tasks = Task.query.order_by(Task.title).all()
elif sort_query == "desc":
tasks = Task.query.order_by(desc(Task.title)).all()
else:
Comment on lines +61 to +67

Choose a reason for hiding this comment

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

⭐️ Great work handling this query param logic ⭐️

tasks = Task.query.all()

if tasks:
for task in tasks:
task_response.append(task.to_dict())
return jsonify(task_response)


@task_bp.route("/<task_id>", methods=["GET"])
def read_one_task(task_id):
task = validate_model(Task, task_id)
if not task.goal_id:
return {
"task":
{
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": bool(task.completed_at)
}
}
else:
return {
"task":
{
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": bool(task.completed_at),
"goal_id": task.goal_id
}
}



@task_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 {
"task": {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": bool(task.completed_at)
}}

@task_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'})




def handle_slack_api(task_title):
url = "https://slack.com/api/chat.postMessage?channel=task-notifications&text=beep%20boop&pretty=1"

payload = {
"channel": "C0574HS4KL2",
"text": task_title
}

auth_key = {"Authorization": f"Bearer {os.environ.get('AUTHORIZATION')}"}

response = requests.post(url, headers=auth_key, data=payload)

print(response.text)




@task_bp.route("/<task_id>/<task_status>", methods=["PATCH"])
def update_completed_task(task_id, task_status):
task = validate_model(Task, task_id)
if task:
if task_status == "mark_complete":
task.completed_at = datetime.now()
# call to API helper fuction here
handle_slack_api(task.title)
elif task_status == "mark_incomplete":
task.completed_at = None

db.session.commit()

return {
"task": {
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": bool(task.completed_at)
}}

# Wave 5: Creating a Second Model Goal
# Make a POST request
# Create a Valid Goal
@goal_bp.route("", methods=["POST"])
def handle_goals():
request_body = request.get_json()

if len(request_body) < 1:
return {"details": "Invalid data"}, 400
else:
new_goal = Goal(
title = request_body["title"]
)

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

return {
"goal":
{
"id": new_goal.goal_id,
"title": new_goal.title
}
}, 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 👍🏾


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

goals = Goal.query.all()

if goals:
for goal in goals:
goal_response.append({
"id": goal.goal_id,
"title": goal.title
})
return jsonify(goal_response), 200

@goal_bp.route("/<goal_id>", methods=["GET"])
def read_goal(goal_id):
goal = validate_model(Goal, goal_id)
return {
"goal": {
"id": goal.goal_id,
"title": goal.title
}
}, 200


@goal_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.add(goal)
db.session.commit()

return {
"goal": {
"id": goal.goal_id,
"title": goal.title
}}, 200


@goal_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'})


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

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


db.session.commit()
return {
"id": goal.goal_id,
"task_ids": request_body["task_ids"]
}



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

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

for task in goal.tasks:

goals_tasks["tasks"].append(task.to_dict())

return goals_tasks, 200
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ blinker==1.4
certifi==2020.12.5
chardet==4.0.0
click==7.1.2
coverage==7.2.5
Flask==1.1.2
Flask-Migrate==2.6.0
Flask-SQLAlchemy==2.4.4
Expand All @@ -30,5 +31,6 @@ requests==2.25.1
six==1.15.0
SQLAlchemy==1.3.23
toml==0.10.2
tomli==2.0.1
urllib3==1.26.5
Werkzeug==1.0.1
Loading