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

C17/Bahareh/Sharks #108

Open
wants to merge 5 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()'
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ At submission time, no matter where you are, submit the project via Learn.
This project is designed to fulfill the features described in detail in each wave. The tests are meant to only guide your development.

1. [Setup](ada-project-docs/setup.md)
1. [Testing] (ada-project-docs/testing.md)
1. [Testing](ada-project-docs/testing.md)
1. [Wave 1: CRUD for one model](ada-project-docs/wave_01.md)
1. [Wave 2: Using query params](ada-project-docs/wave_02.md)
1. [Wave 3: Creating custom endpoints](ada-project-docs/wave_03.md)
Expand Down
4 changes: 4 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,9 @@ def create_app(test_config=None):
migrate.init_app(app, db)

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

return app
5 changes: 4 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@


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", backref="goal", lazy=True)

39 changes: 38 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
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, nullable=False)
title = db.Column(db.String)
description = db.Column(db.String)
completed_at = db.Column(db.DateTime,default=None,nullable=True)
goals = db.relationship("Goal")
goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'), nullable=True)



def to_json(self):
if self.completed_at:
is_completed=True
else:
is_completed=False
task_dict= {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": is_completed,
}
if self.goal_id:
task_dict["goal_id"] = self.goal_id
return task_dict

def to_json_without_des(self):
if self.completed_at:
is_completed=True
else:
is_completed=False
return {
"id": self.task_id,
"title": self.title,
"description": "",
"is_complete": is_completed,
}
Comment on lines +15 to +40

Choose a reason for hiding this comment

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

👍 ohhh cool! Interesting way to separate out which tasks need which attributes! I wonder if we could combine these two methods back together with an if statement, though. Something to think about!



287 changes: 286 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1 +1,286 @@
from flask import Blueprint

from app.models.task import Task
from app.models.goal import Goal
from app import db
from flask import Blueprint, jsonify,abort,make_response,request
from sqlalchemy.sql.functions import now
import os
import requests


task_list_bp = Blueprint("task_list", __name__,url_prefix="/tasks")
goal_list_bp = Blueprint("goal_list", __name__,url_prefix="/goals")
Comment on lines +11 to +12

Choose a reason for hiding this comment

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

one thing we could do to help with too much in one file is by separating these two blueprints out into their own files. This can help bigger teams work on different tasks in a project without stepping on each other's toes.



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({"message":f"task {task_id} not found"}, 404))

return task
Comment on lines +15 to +26

Choose a reason for hiding this comment

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

we could put this in a separate file to keep our routes file tidier




Comment on lines +27 to +29

Choose a reason for hiding this comment

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

Suggested change


#Get tasks sorted or get all tasks
@task_list_bp.route("", methods=["GET"])
def get_tasks_sorted():

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


if sort_query and sort_query=="asc":
tasks= Task.query.order_by(Task.title.asc())
elif sort_query and sort_query=="desc":
tasks= Task.query.order_by(Task.title.desc())
else:
tasks = Task.query.all()



Comment on lines +44 to +46

Choose a reason for hiding this comment

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

be careful with too much blank lines! it can make code harder to read

Suggested change

task_response = []
for task in tasks:
task_response.append(task.to_json())
return make_response( jsonify(task_response),200)



Comment on lines +51 to +53

Choose a reason for hiding this comment

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

Suggested change


#Get one task
@task_list_bp.route("/<task_id>", methods=["GET"])
def get_one_task(task_id):

task = Task.query.get(task_id)

if task:
return {
"task": task.to_json()
}
Comment on lines +62 to +64

Choose a reason for hiding this comment

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

this works, but don't forget your status code! every response we return to our client should be consistent and predictable

Suggested change
return {
"task": task.to_json()
}
return jsonify({"task": task.to_json()}), 200

else:
return make_response(jsonify(None), 404)



#Create one task
@task_list_bp.route("", methods=["POST"])
def create_new_task():

Choose a reason for hiding this comment

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

👍


request_body = request.get_json()
if "title" not in request_body or "description" not in request_body:
return make_response({"details":f"Invalid data"}, 400)

new_task = Task(title=request_body["title"],
description=request_body["description"],
)
if "completed_at" in request_body:
new_task.completed_at = request_body["completed_at"]


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

return jsonify({"task":new_task.to_json()}),201



#Update one task
@task_list_bp.route("/<task_id>", methods=["PUT"])
def update_task(task_id):

Choose a reason for hiding this comment

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

👍

task = validate_task(task_id)

request_body = request.get_json()

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

db.session.commit()

return jsonify({"task":task.to_json()}),200


#Delete one task
@task_list_bp.route("/<task_id>", methods=["DELETE"])

Choose a reason for hiding this comment

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

👍

def delete_task(task_id):
task = validate_task(task_id)

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

return make_response({"details":f'Task {task.task_id} \"{task.title}\" successfully deleted'}),200




#Mark complete for one task and use Slack API
@task_list_bp.route('/<task_id>/mark_complete', methods=['PATCH'])

Choose a reason for hiding this comment

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

👍

def mark_complete(task_id):
task=Task.query.get(task_id)
if not task:
abort(make_response({"message":f"task {task_id} not found"}, 404))
task.completed_at = now()
db.session.add(task)
db.session.commit()


slack_api_url = "https://slack.com/api/chat.postMessage"
params = {
"channel" : "test-channel",
"text" : f"Someone just completed the task {task.title}"
}
headers = {
"Authorization" : f"Bearer {os.environ.get('SLACK_API_HEADER')}"
}
requests.get(url=slack_api_url, params=params, headers=headers)

return make_response(jsonify({"task" : task.to_json()}))


#Mark incomplete for one task
@task_list_bp.route('/<task_id>/mark_incomplete', methods=['PATCH'])
def mark_incomplete(task_id):
task=Task.query.get(task_id)
if not task:
abort(make_response({"message":f"task {task_id} not found"}, 404))
task.completed_at = None
db.session.add(task)
db.session.commit()
task_response={"task":{
"id": task.task_id,
"title": task.title,
"description": task.description, "is_complete": False
}}
Comment on lines +153 to +157

Choose a reason for hiding this comment

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

we could turn this into a helper method

return jsonify(task_response),200

#*******************************Goal_routes*******************************************

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({"message":f"goal {goal_id} not found"}, 404))

return goal
Comment on lines +162 to +173

Choose a reason for hiding this comment

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

This would be another function we could move into another file



#Create one goal
@goal_list_bp.route("", methods=["post"])
def create_new_goal():

request_body = request.get_json()
if "title" not in request_body:
return {
"details": "Invalid data"
},400
new_goal = Goal(title=request_body["title"],
)
Comment on lines +185 to +186

Choose a reason for hiding this comment

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

Suggested change
new_goal = Goal(title=request_body["title"],
)
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



#Get one goal
@goal_list_bp.route("/<goal_id>", methods=["GET"])
def get_one_goal(goal_id):
goal = validate_goal(goal_id)

return{"goal": {
"id": goal.goal_id,
"title": goal.title,
}}
Comment on lines +201 to +204

Choose a reason for hiding this comment

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

don't forget your status code!



#Get all goals
@goal_list_bp.route("", methods=["GET"])
def get_all_goal():
goals = Goal.query.all()

goal_response = []
for goal in goals:
goal_response.append({
"id": goal.goal_id,
"title": goal.title,

})
return jsonify(goal_response),200


#Update one goal
@goal_list_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"]
goal.decription = request_body["description"]


db.session.commit()

return make_response(jsonify(f"goal #{goal.goal_id} successfully updated")),200


#Delete one goal
@goal_list_bp.route("/<goal_id>", methods=["DELETE"])
def delete_goal(goal_id):
goal = validate_goal(goal_id)

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

return {"details":f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"}


#********************************Nested routes************************************

#Show tasks for a goal
@goal_list_bp.route("/<goal_id>/tasks", methods=["GET"])
def show_tasks_for_a_goal(goal_id):
goal = validate_goal(goal_id)
tasks = Task.query.filter_by(goal=goal)
task_list = []

for task in tasks:
task_list.append(task.to_json())
return make_response(jsonify({
"id": goal.goal_id,
"title": goal.title,
"tasks": task_list
Comment on lines +256 to +263

Choose a reason for hiding this comment

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

just as we made a to_json method for tasks, we could do the same for goals

}), 200)


#Post tasks to a goal
@goal_list_bp.route("/<goal_id>/tasks", methods=["POST"])

Choose a reason for hiding this comment

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

👍

def posts_tasks_to_a_goal(goal_id):
goal = validate_goal(goal_id)



Comment on lines +271 to +273

Choose a reason for hiding this comment

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

Suggested change

request_body = request.get_json()
for task_id in request_body["task_ids"]:
task = Task.query.get(task_id)

task.goal_id=goal.goal_id
if not task:
abort(make_response({"message":f"task {task_id} invalid"}, 400))


goal.tasks.append(task)
db.session.commit()

return make_response(jsonify({"id":goal.goal_id,"task_ids":request_body["task_ids"]}),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