Skip to content

Scissors_Gloria #73

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 16 commits into
base: master
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
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: gunicorn 'app:create_app()'
10 changes: 10 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_cors import CORS
import os
from dotenv import load_dotenv

Expand All @@ -27,8 +28,17 @@ def create_app(test_config=None):
from app.models.goal import Goal

db.init_app(app)
CORS(app)
migrate.init_app(app, db)



# Register Blueprints here
from .routes import task_list_bp, goal_list_bp
app.register_blueprint(task_list_bp)
app.register_blueprint(goal_list_bp)




return app
14 changes: 13 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,16 @@


class Goal(db.Model):
goal_id = db.Column(db.Integer, primary_key=True)
goal_id = db.Column(db.Integer, autoincrement=True, primary_key=True)
title = db.Column(db.String, nullable = False)
tasks = db.relationship(
'Task', backref='goal', lazy=True
)
# task_id = db.Column(db.Integer, autoincrement=True, primary_key=True)

def serialize(self):
result = {
'id': self.goal_id,
'title': self.title
}
return result
49 changes: 48 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,53 @@
from flask import current_app
from requests.models import parse_header_links
from app import db
import requests
import os


class Task(db.Model):
task_id = db.Column(db.Integer, primary_key=True)
task_id = db.Column(db.Integer, autoincrement=True, primary_key=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'),nullable = True)

def serialize(self):
result = {
'id': self.task_id,
'title': self.title,
'description': self.description,
'is_complete': self.completed_at != None
}
if self.goal_id:
result['goal_id'] = self.goal_id
return result

# def serialize_two(self):

Choose a reason for hiding this comment

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

You're on the right track with this alternative helper instance method! What we need is to use serialize when goal_id is None (the task doesn't belong to a goal), and serialize_two when goal_id is a truthy value (the task does belong to a goal)

# result = {
# 'id': self.task_id,
# 'goal_id': self.goal_id,
# 'title': self.title,
# 'description': self.description,
# 'is_complete': self.completed_at != None
# }
# return result

def notify_slack(self):

Choose a reason for hiding this comment

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

Great work encapsulating this functionality in a instance method.

message = f"Someone just completed the task {self.title}"
url = "https://slack.com/api/chat.postMessage"
params = {

"channel":"task-notifications",
"text": message
}
headers = {
"Authorization": os.environ.get('SLACK_API')
}
r = requests.post(url, data = params, headers = headers)
r.status_code





264 changes: 264 additions & 0 deletions app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,266 @@
from flask_sqlalchemy.model import camel_to_snake_case
from tests.test_wave_01 import test_create_task_must_contain_description
from flask import Blueprint
from app import db
from app.models.task import Task
from app.models.goal import Goal
from flask import request, Blueprint, make_response, jsonify
from datetime import datetime
import requests


def order_by_title(task_response):
return task_response["title"]

goal_list_bp = Blueprint("goal_list", __name__, url_prefix = '/goals')

@goal_list_bp.route('', methods = ['GET','POST'])
def handle_goals():
if request.method == 'GET':
goals = Goal.query.all()
goal_response = []
for goal in goals:
goal_response.append(goal.serialize())
return jsonify(goal_response)

elif request.method == 'POST':
request_body = request.get_json()
if "title" in request_body:
new_goal = Goal(
title = request_body['title']
)
db.session.add(new_goal)
db.session.commit()
return{
'goal':
new_goal.serialize()
},201
else:
return({
"details": f'Invalid data'
},400)

@goal_list_bp.route('/<goal_id>', methods = ['GET','PUT', 'DELETE'])
def handle_goal(goal_id):
goal = Goal.query.get(goal_id)
if not goal:
return "", 404

if request.method == 'GET':
#check expected output in test
return({
'goal':
goal.serialize()

})
elif request.method == 'PUT':
request_body = request.get_json()
if 'title' in request_body:
goal.title = request_body['title']
db.session.commit()
return({
'goal': goal.serialize()
},200)

elif request.method =='DELETE':
db.session.delete(goal)
db.session.commit()
return({
"details": f'Goal {goal_id} "{goal.title}" successfully deleted'
},200)

@goal_list_bp.route('/<goal_id>/tasks', methods = ['GET'])
def get_tasks_for_goal(goal_id):
goal = Goal.query.get(goal_id)
if not goal:
return "", 404

tasks = goal.tasks
json_tasks = []
for task in tasks:
serializeds_task = task.serialize() # dict
serializeds_task['goal_id'] = int(goal_id)
json_tasks.append(serializeds_task)


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

@goal_list_bp.route('/<goal_id>/tasks', methods = ['POST'])
def add_task_to_goal(goal_id):
goal = Goal.query.get(goal_id)
tasks = request.json
tasks = tasks["task_ids"]
if not goal:
return "", 404
# task = db.session.query(Task.id)
query = db.session.query(Task).filter(Task.task_id.in_(tasks))

results = query.all()
for task in results:
task.goal_id = goal.goal_id
db.session.commit()

return{
'id': goal.goal_id,
'task_ids': tasks
},200





task_list_bp = Blueprint("task_list", __name__, url_prefix='/tasks')
@task_list_bp.route('', methods=['GET', 'POST'])
def handle_tasks():
if request.method == 'GET':
#return full list of tasks

# allows access to keys - sort
arg = request.args # better name
if "sort" in arg:
if arg['sort'] == "asc":
tasks = Task.query.order_by(Task.title.asc())
elif arg['sort'] == "desc":
tasks = Task.query.order_by(Task.title.desc())
else:
tasks = Task.query.all()
task_response = []
for task in tasks:
task_response.append(task.serialize())

return jsonify(task_response)

elif request.method == 'POST':
request_body = request.get_json()
if "title" not in request_body:
return {
"details": "Invalid data"
}, 400
elif "description" not in request_body:
return {
"details": "Invalid data"
}, 400
elif "completed_at" not in request_body:
return {
"details": "Invalid data"
}, 400
Comment on lines +139 to +150

Choose a reason for hiding this comment

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

We could combine this in a compound conditional statement to DRY up the code:

if "title not in request_body or "description not in request body or "completed_at" not in request_body: 
       return {"details": "Invalid data"}, 400

stringify_format = "%a, %d %b %Y %H:%M:%S %Z"
if request_body["completed_at"]:

new_task = Task(
title = request_body['title'],
description = request_body['description'],
completed_at = datetime.strptime(request_body["completed_at"], stringify_format)
)
else:
new_task = Task(
title = request_body['title'],
description = request_body['description'],
)
db.session.add(new_task)
db.session.commit()
return{
'task':{
'id': new_task.task_id,
'title': new_task.title,
'description': new_task.description,
'is_complete': new_task.completed_at != None

Choose a reason for hiding this comment

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

Consider moving the logic to convert completed_at to a True or False value into an instance method and use the serialize instance method here.

}
},201


@task_list_bp.route('/<task_id>', methods=['GET','PUT', 'DELETE'])
def handle_task(task_id): # same name as parameter route
task = Task.query.get(task_id)
if not task:
return "", 404

if request.method == 'GET':
return({
'task': task.serialize()
})
elif request.method == 'DELETE':
db.session.delete(task)
db.session.commit()
return({
"details": f'Task {task_id} "{task.title}" successfully deleted'
},200)


elif request.method =='PUT':
request_body = request.get_json()
if 'title' in request_body:
task.title = request_body['title']
if 'description' in request_body:
task.description = request_body['description']
if 'completed_at' in request_body:
task.completed = request_body['completed_at']
if 'goal_id' in request_body:
task.goal_id= request_body['goal_id']
db.session.commit()
return({
'task': task.serialize()
},200)

@task_list_bp.route('/<task_id>/mark_complete',methods = ['PATCH'])
def mark_complete_task(task_id):
task = Task.query.get(task_id)
if not task:
return "Task not found", 404
task.completed_at = datetime.utcnow()
task.notify_slack()
db.session.commit()
return{
'task':{
'id': task.task_id,
'title': task.title,
'description': task.description,
'is_complete': task.completed_at != None
}
Comment on lines +218 to +223

Choose a reason for hiding this comment

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

Notice that this code is repeated below. This is a great place to use an instance method.

},200
@task_list_bp.route('/<task_id>/mark_incomplete',methods = ['PATCH'])
def mark_incomplete_task(task_id):
task = Task.query.get(task_id)
if not task:
return "Task Not Found", 404
task.completed_at = None
db.session.commit()
return{
'task':{
'id': task.task_id,
'title': task.title,
'description': task.description,
'is_complete': task.completed_at != None
}
},200



























11 changes: 11 additions & 0 deletions app/task-list-api.code-workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"folders": [
{
"path": ".."
},
{
"path": "../../api/solar-system-api"
}
],
"settings": {}
}
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