Skip to content

Lin - Rocks! #63

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 18 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()'
9 changes: 6 additions & 3 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
import os
from flask_sqlalchemy import SQLAlchemy
from dotenv import load_dotenv

import os

db = SQLAlchemy()
migrate = Migrate()
Expand All @@ -30,5 +29,9 @@ def create_app(test_config=None):
migrate.init_app(app, db)

# Register Blueprints here
from .routes import tasks_bp
app.register_blueprint(tasks_bp)
from .routes import goals_bp
app.register_blueprint(goals_bp)

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


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)
# 'Task' looks at class in python and loads multiple of those (this is like a pseudo column)
tasks = db.relationship('Task', backref='goal', lazy=True)

def to_dict(self):

Choose a reason for hiding this comment

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

👍 yay helper method!!

return {
"id": self.goal_id,
"title": self.title,
}
35 changes: 33 additions & 2 deletions app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
from flask import current_app
from app import db
from flask import current_app
from sqlalchemy import DateTime

Choose a reason for hiding this comment

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

since we are already importing db, we don't need to import DateTime separately. It already comes in db, like db.String, db.Column, etc.

Suggested change
from sqlalchemy import DateTime



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)

# lowercase 'goal.id' looks at a table in your db
goal_id = db.Column(db.Integer, db.ForeignKey(
'goal.goal_id'), nullable=True)

def is_complete(self):

Choose a reason for hiding this comment

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

👍

if self.completed_at:
return True
else:
return False

def to_json(self):

Choose a reason for hiding this comment

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

👍

return {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": self.is_complete()
}

def with_goal(self):
return {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": self.is_complete(),
"goal_id": self.goal_id
}
Comment on lines +30 to +37

Choose a reason for hiding this comment

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

we could use an if statement to combine with_goal() and to_json()

217 changes: 216 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,217 @@
from flask import Blueprint
from app import db
from app.models.task import Task
from datetime import datetime
from flask import request, Blueprint, make_response, jsonify
import requests
import os
# from slack_sdk import WebClient
# from slack_sdk.errors import SlackApiError
Comment on lines +7 to +8

Choose a reason for hiding this comment

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

since you aren't using these imports, remember to delete them from the final submission

Suggested change
# from slack_sdk import WebClient
# from slack_sdk.errors import SlackApiError

from app.models.goal import Goal


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


# -------------------------
# WAVE 1 - TASK ENDPOINTS
# -------------------------
@tasks_bp.route("", methods=["POST"], strict_slashes=False)
def create_task():
request_body = request.get_json()
if "title" in request_body and "description" in request_body and "completed_at" in request_body:
new_task = Task(
title=request_body["title"],
description=request_body["description"],
completed_at=request_body["completed_at"]
)
db.session.add(new_task)
db.session.commit()
return jsonify({"task": new_task.to_json()}), 201
else:
return make_response({"details": "Invalid data"}, 400)


# WAVE 2
@tasks_bp.route("", methods=["GET"], strict_slashes=False)
def task_index():
sort_query = request.args.get("sort")
if sort_query == "asc":
tasks = Task.query.order_by(Task.title)
elif sort_query == "desc":
tasks = Task.query.order_by(Task.title.desc())
else:
tasks = Task.query.all()
tasks_response = [(task.to_json()) for task in tasks]
return make_response(jsonify(tasks_response), 200)


# WAVE 1
@tasks_bp.route("/<task_id>", methods=["GET"], strict_slashes=False)
def get_one_task(task_id):
task = Task.query.get(task_id)
if task is None:
return make_response("", 404)
Comment on lines +54 to +56

Choose a reason for hiding this comment

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

we could shorten this by using get_or_404() method instead

Suggested change
task = Task.query.get(task_id)
if task is None:
return make_response("", 404)
task = Task.query.get_or_404(task_id)

# thank you audrey!
elif task.goal_id is None:
return jsonify({"task": task.to_json()}), 200
else:
return jsonify({"task": task.with_goal()}), 200
Comment on lines +58 to +61

Choose a reason for hiding this comment

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

if we combined your two helper methods in Task, we could combine these lines into one return statement



@tasks_bp.route("/<task_id>", methods=["PUT"], strict_slashes=False)
def update_task(task_id):
task = Task.query.get(task_id)
if task is None:
return make_response("", 404)
Comment on lines +66 to +68

Choose a reason for hiding this comment

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

here we could use get_or_404(), too!

else:
form_data = request.get_json()
task.title = form_data["title"]
task.description = form_data["description"]
task.completed_at = form_data["completed_at"]
db.session.commit()
return jsonify({"task": task.to_json()}), 200


@tasks_bp.route("/<task_id>", methods=["DELETE"], strict_slashes=False)
def delete_task(task_id):
task = Task.query.get(task_id)
if task is None:
return make_response("", 404)
else:
db.session.delete(task)
db.session.commit()
task_response = {
"details": f'Task {task.task_id} "{task.title}" successfully deleted'}
return make_response(task_response), 200


# WAVE 3
@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"], strict_slashes=False)
def handle_incomplete(task_id):
task = Task.query.get(task_id)
if task is None:
return make_response("", 404)
Comment on lines +94 to +96

Choose a reason for hiding this comment

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

here we could use get_or_404(), too!

else:
task.completed_at = None
db.session.commit()
return jsonify({"task": task.to_json()}), 200


@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"], strict_slashes=False)
def handle_complete(task_id):
task = Task.query.get(task_id)
if task is None:
return make_response("", 404)
Comment on lines +105 to +107

Choose a reason for hiding this comment

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

here we could use get_or_404(), too!

else:
task.completed_at = datetime.now()
db.session.commit()
call_slack_api(task)
return jsonify({"task": task.to_json()}), 200


# WAVE 4
def call_slack_api(task):

Choose a reason for hiding this comment

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

:+1 great job turning this into a helper function

SLACK_TOKEN = os.environ.get("SLACK_BOT_TOKEN")
url = "https://slack.com/api/chat.postMessage"
payload = {
"channel": "task-notifications",
"text": f"Someone just completed the task {task.title}"}
headers = {
"Authorization": f"Bearer {SLACK_TOKEN}",
}
return requests.request("POST", url, headers=headers, data=payload)

# IGNORE - WORKS BUT USES SLACK BOLT/PYTHON SDK (from slack API docs) instead of requests.package
# def call_slack_api(task):
# client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN"))
# channel_id = "task-notifications"
# # try:
# result = client.chat_postMessage(
# channel=channel_id,
# text=f"Someone just completed the task {task.title}")
# return result
Comment on lines +127 to +135

Choose a reason for hiding this comment

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

Glad you found an alternate way to send a Slack message! Remember, though, if it's unused code you should get rid of it before final submission



# -------------------------
# WAVE 5 - GOAL ENDPOINTS
# -------------------------
@goals_bp.route("", methods=["POST"], strict_slashes=False)
def create_goal():
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 jsonify({"goal": new_goal.to_dict()}), 201
return make_response({"details": "Invalid data"}, 400)


@goals_bp.route("", methods=["GET"], strict_slashes=False)
def goal_index():
goals = Goal.query.all()
goals_response = [(goal.to_dict()) for goal in goals]

Choose a reason for hiding this comment

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

👍 nice list comprehension

return make_response(jsonify(goals_response), 200)


@goals_bp.route("/<goal_id>", methods=["GET"], strict_slashes=False)
def get_one_goal(goal_id):
goal = Goal.query.get(goal_id)
if goal is None:
return make_response("", 404)
Comment on lines +162 to +164

Choose a reason for hiding this comment

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

here we could use get_or_404(), too!

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


@goals_bp.route("/<goal_id>", methods=["PUT"], strict_slashes=False)
def update_goal(goal_id):
goal = Goal.query.get(goal_id)
if goal is None:
return make_response("", 404)
Comment on lines +170 to +172

Choose a reason for hiding this comment

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

here we could use get_or_404(), too!

else:
form_data = request.get_json()
goal.title = form_data["title"]
db.session.commit()
return jsonify({"goal": goal.to_dict()}), 200


@goals_bp.route("/<goal_id>", methods=["DELETE"], strict_slashes=False)
def delete_goal(goal_id):
goal = Goal.query.get(goal_id)
if goal is None:
return make_response("", 404)
Comment on lines +182 to +184

Choose a reason for hiding this comment

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

here we could use get_or_404(), too!

else:
db.session.delete(goal)
db.session.commit()
goal_response = {
"details": f'Goal {goal.goal_id} "{goal.title}" successfully deleted'}
return make_response(goal_response), 200


# WAVE 6
@goals_bp.route("/<goal_id>/tasks", methods=["POST"], strict_slashes=False)
def sending_list_tasks_to_goal(goal_id):
request_body = request.get_json()
tasks = request_body["task_ids"]
# (db)
goal = Goal.query.get(goal_id)
for task_id in tasks:
task_db_object = Task.query.get(task_id)
goal.tasks.append(task_db_object)
# task_db_object.goal_id = int(goal_id)
db.session.commit()
return {"id": goal.goal_id,
"task_ids": tasks}, 200


@goals_bp.route("/<goal_id>/tasks", methods=["GET"], strict_slashes=False)
def getting_tasks_of_one_goal(goal_id):
goal = Goal.query.get(goal_id)
if goal is None:
return make_response("", 404)
Comment on lines +211 to +213

Choose a reason for hiding this comment

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

here we could use get_or_404(), too!

tasks = Task.query.join(Goal).filter(Task.goal_id == goal_id).all()
# tasks_response = []
tasks_response = [(task.with_goal()) for task in tasks]
return{"id": goal.goal_id, "title": goal.title, "tasks": tasks_response}, 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.
45 changes: 45 additions & 0 deletions migrations/alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# A generic, single database configuration.

[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false


# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
Loading