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 - Abby Goodman #129

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
10 changes: 8 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ 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("RENDER_DB_URI")

Choose a reason for hiding this comment

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

👍🏾


# app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
# "SQLALCHEMY_DATABASE_URI")
else:
app.config["TESTING"] = True
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
Expand All @@ -30,5 +32,9 @@ def create_app(test_config=None):
migrate.init_app(app, db)

Choose a reason for hiding this comment

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

Great job! We need to pass our instance of our app & the instance of our SQLALchemy db to connect the db and migrate to our Flask app


# Register Blueprints here
from .routes import task_list_bp
app.register_blueprint(task_list_bp)
from .routes import goals_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
24 changes: 23 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,26 @@


class Goal(db.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=True)
Comment on lines 4 to +7

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 Abby! 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.





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

return new_goal
Comment on lines +13 to +18

Choose a reason for hiding this comment

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

I noticed you created this class method but didn't make use of it






def to_dict_goal(self):
return{ "goal": {
"id":self.goal_id,
"title":self.title}}
39 changes: 38 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,41 @@


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) #when refactoring change to varchar
description = db.Column(db.String) # when refacoring change to varvhar
completed_at = db.Column(db.DateTime, default=None, nullable=True)
goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'))
goal = db.relationship("Goal", back_populates="tasks")
Comment on lines 4 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=task_data["completed_at"]
)

return new_task
Comment on lines +14 to +21

Choose a reason for hiding this comment

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

I noticed you created this class method but didn't make use of it






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

Choose a reason for hiding this comment

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

👍🏾

# wave 1
#Our task list API should be able to work with an entity called Task.
# Tasks are entities that describe a task a user wants to complete. They contain a:
# title to name the task
# description to hold details about the task
# an optional datetime that the task is completed on
# Our goal for this wave is to be able to create, read, update, and delete different tasks.
# We will create RESTful routes for this different operations.


246 changes: 245 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1 +1,245 @@
from flask import Blueprint
from app import db
from app.models.task import Task
from app.models.goal import Goal
from flask import Blueprint, jsonify, make_response, request, abort

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 datetime import datetime
import requests
import json
from dotenv import load_dotenv

Choose a reason for hiding this comment

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

Great work! We need to import load_dotenv to set up environment variables in our .env file

import os

load_dotenv()
task_list_bp = Blueprint("task_list", __name__, url_prefix="/tasks")
goals_bp = Blueprint("goals_list", __name__, url_prefix="/goals")
#validating the task_id
def validate_task(task_id):
try:
task_id = int(task_id)
except:
abort(make_response({"message": f"Task {task_id} invalid"}, 400))
task = Task.query.get(task_id)
if not task:
abort(make_response({"details": "Invalid Data"}, 404))
return task
Comment on lines +15 to +23

Choose a reason for hiding this comment

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

Nice approach to handling an invalid task 😁


#validate goal_id
def validate_goal(goal_id):
try:
goal_id = int(goal_id)
except:
abort(make_response({"message": f"Goal {goal_id} invalid"}, 400))
goal = Goal.query.get(goal_id)
if not goal:
abort(make_response({"details": "Invalid Data"}, 404))
return goal
Comment on lines +26 to +34

Choose a reason for hiding this comment

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

Nice approach to handling an invalid goal 😁 There's a bunch of repeated code here, any ideas of how to refactor?


# create tasks
@task_list_bp.route("", methods=["POST"])
def post_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(
title = request_body["title"],
description = request_body["description"],
# completed_at = request_body["completed_at"]

Choose a reason for hiding this comment

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

It’s good practice to avoid committing commented out code when submitting a PR.

)
db.session.add(new_task)
db.session.commit()
Comment on lines +47 to +48

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

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

#create goals
@goals_bp.route("", methods=["POST"])
def post_goal():
request_body = request.get_json()
if "title" not in request_body:
return make_response({"details": "Invalid data"}, 400)
new_goal = Goal(
title = request_body["title"]
)
db.session.add(new_goal)
db.session.commit()
return jsonify({
"goal": {
"id": new_goal.goal_id,
"title": new_goal.title} }), 201
# create nested post
@goals_bp.route("/<goal_id>/tasks", methods=["POST"])
def create_task(goal_id):
goal = validate_goal(goal_id)
request_body = request.get_json()
result = {"id": goal.goal_id,
"task_ids": request_body["task_ids"]
}
for task_id in request_body["task_ids"]:
task = validate_task(task_id)
goal.tasks.append(task)
db.session.commit()
return result

# create nested get, singular goal, with its many tasks
@goals_bp.route("<goal_id>/tasks", methods=["GET"])
def get_tasks_one_goal(goal_id):
goal = validate_goal(goal_id)
task_response = []



for task in goal.tasks:
if not task.completed_at:
task_response.append({
"id": task.task_id,
"goal_id":goal.goal_id,
"title":task.title,
"description": task.description,
"is_complete": False

})
else:
task_response.append({
"id": task.task_id,
"goal_id": goal.goal_id,
"title": task.title,
"description": task.description,
"completed_at":task.completed_at
})
return jsonify({
"id": goal.goal_id,
"title": goal.title,
"tasks": task_response})
# #read all tasks
@task_list_bp.route("", methods=["GET"])
def get_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(Task.title.desc()).all()
else:
tasks = Task.query.all()
for task in tasks:
if not task.completed_at:
task_response.append({"id":task.task_id,
"title":task.title,
"description": task.description,
"is_complete": False

})
else:
task_response.append({
"id":task.task_id,
"title":task.title,
"description": task.description,
"completed_at":task.completed_at
})
return jsonify(task_response)

#read all goals
@goals_bp.route("", methods=["GET"])
def read_all_goals():
goal_response = []
goals = Goal.query.all()

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

return jsonify(goal_response)
#read one task/ read if empty task.
@task_list_bp.route("/<task_id>", methods=["GET"])
def get_one_task(task_id):
task = validate_task(task_id)
# goal = validate_goal(goal_id)

if task.goal_id:
return{"task": {
"id":task.task_id,
"goal_id": task.goal_id,
"title":task.title,
"description":task.description,
"is_complete": True if task.completed_at else False}}
else:
return task.to_dict()
#read one goal
@goals_bp.route("/<goal_id>", methods=["GET"])
def get_one_goal(goal_id):
goal = validate_goal(goal_id)


return jsonify({"goal":{"id": goal.goal_id,
"title":goal.title}})
# #update task
@task_list_bp.route("/<task_id>", methods=["PUT"])
def update_task(task_id):

task = validate_task(task_id)
request_body = request.get_json()
task.title = request_body["title"]
task.description = request_body["description"]
db.session.commit()
return task.to_dict()

#update goal
@goals_bp.route("/<goal_id>", methods=["PUT"])
def update_goal(goal_id):
goal = validate_goal(goal_id)
request_body = request.get_json()
goal.title = request_body["title"]
db.session.commit
return goal.to_dict_goal()

#mark_complete endpoint with slack api implementation
@task_list_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def update_task_to_complete(task_id):
task = validate_task(task_id)
task.completed_at = datetime.now()
#slack implementation
url = "https://slack.com/api/chat.postMessage"
payload = json.dumps({
"channel": "C0581AUJACV",
"text": (f"Someone just completed the task {task.title}")
})
headers = {
'Authorization': os.environ.get("SLACK_API_TOKEN"),
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
db.session.commit()
return task.to_dict(), 200
Comment on lines +205 to +221

Choose a reason for hiding this comment

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

This function is pretty packed, for readability I suggest using more spacing in between lines. Something like this:

def update_task_to_complete(task_id):
    task = validate_task(task_id)
    task.completed_at = datetime.now()
    
    #slack implementation
    url = "https://slack.com/api/chat.postMessage"

    payload = json.dumps({
        "channel": "C0581AUJACV",
        "text": (f"Someone just completed the task {task.title}")
    })

    headers = {
        'Authorization': os.environ.get("SLACK_API_TOKEN"),
        'Content-Type': 'application/json'
    }

    response = requests.request("POST", url, headers=headers, data=payload)
    print(response.text)

    db.session.commit()

    return task.to_dict(), 200


#mark_incomplete endpoint
@task_list_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def update_task_to_incomplete(task_id):
task = validate_task(task_id)
task.completed_at = None
db.session.commit()
return task.to_dict()

# delete task
@task_list_bp.route("/<task_id>", methods=["DELETE"])
def delete_task(task_id):
task = validate_task(task_id)
db.session.delete(task)
db.session.commit()
return abort(make_response({"details":f"Task {task.task_id} \"{task.title}\" successfully deleted"}))

#delete goal:
@goals_bp.route("/<goal_id>", methods=["DELETE"])
def delete_goal(goal_id):
goal = validate_goal(goal_id)
db.session.delete(goal)
db.session.commit()
return abort(make_response({"details":f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"}))
Loading