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

Scissors- Marisa M #70

Open
wants to merge 10 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
Binary file added .noseids
Binary file not shown.
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()'
8 changes: 5 additions & 3 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
import os
from dotenv import load_dotenv


db = SQLAlchemy()
migrate = Migrate()
load_dotenv()


def create_app(test_config=None):
app = Flask(__name__)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
Expand All @@ -29,6 +27,10 @@ def create_app(test_config=None):
db.init_app(app)
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
24 changes: 23 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
from flask import current_app
from app import db


class Goal(db.Model):
goal_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
tasks = db.relationship("Task", backref="goal", lazy=True)

def create_response(self):
return{
"id": self.goal_id,
"title": self.title,
}

def return_tasks(self):
return {
"id": self.goal_id,
"task_ids": self.tasks
}
def return_goal_tasks(self):
tasks_list = []
for task in self.tasks:
tasks_list.append(task.make_json())
return{
"id": self.goal_id,
"title": self.title,
"tasks": tasks_list
}
28 changes: 26 additions & 2 deletions app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
from flask import current_app
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"), nullable=True)

def is_complete(self):
return self.completed_at != None


def make_json(self):

Choose a reason for hiding this comment

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

Good use of a helper method. Consider DRYing up your code by building the dictionary

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

and conditionally adding the goal_id like this:

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

and then returning {"task": json}

if self.goal_id == None:
return {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": self.is_complete()
}
else:
return {
"id": self.task_id,
"goal_id": self.goal_id,
"title": self.title,
"description": self.description,
"is_complete": self.is_complete()
}
184 changes: 183 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,184 @@
from flask import Blueprint
from werkzeug.wrappers import ETagRequestMixin
from app.models.goal import Goal
from flask import Blueprint, request, make_response, jsonify
from app import db
from app.models.task import Task
from sqlalchemy import asc, desc
from datetime import datetime
import requests
import os

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

@tasks_bp.route("", methods=["GET","POST"])
def handle_tasks():
if request.method == "GET":
sort = request.args.get("sort")

if sort == "asc":
tasks = Task.query.order_by(asc("title"))

elif sort == "desc":
tasks = Task.query.order_by(desc("title"))

else:
tasks = Task.query.all()

tasks_response = []
for task in tasks:
tasks_response.append({
"id": task.task_id,
"title": task.title,
"description": task.description,
"is_complete": task.is_complete()
Comment on lines +30 to +33

Choose a reason for hiding this comment

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

We could use the task instance method here task.make_json to DRY up the code and reduce room for errors.

})
return jsonify(tasks_response)

elif request.method == "POST":

Choose a reason for hiding this comment

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

With regards to the point in separating out the functions for each route (that you noted in Learn), this way of organizing your code can increase readability by reducing indentation and making meaningful function names. For instance, if we separate out GET and POST, we might call one function def all_tasks() and the other def create_task().

request_body = request.get_json()

if "title" not in request_body.keys() or "description" not in request_body.keys() or "completed_at" not in request_body.keys():
return make_response({"details": "Invalid data"}, 400)

else:
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 make_response({"task": new_task.make_json()}, 201)

@tasks_bp.route("/<task_id>", methods= ["GET", "PUT", "DELETE"])
def handle_task(task_id):
task = Task.query.get(task_id)

if task is None:
return make_response("", 404)
Comment on lines +53 to +56

Choose a reason for hiding this comment

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


elif request.method == "GET":
return make_response({"task": task.make_json()})

elif request.method == "PUT":
request_body = request.get_json()

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

db.session.commit()

return make_response({"task": task.make_json()})

elif request.method == "DELETE":
db.session.delete(task)
db.session.commit()

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

@tasks_bp.route("/<task_id>/mark_complete", methods= ["PATCH"])
def mark_complete(task_id):
task = Task.query.get(task_id)

if task == None:
return make_response("", 404)

API_KEY = os.environ.get("API_KEY")
PATH = "https://slack.com/api/chat.postMessage"
query_params = {
"channel": "task-notifications",
"text": f"Someone just completed the task {task.title}."
}
task.completed_at = datetime.utcnow()
db.session.commit()
requests.post(PATH, data=query_params, headers={"Authorization":API_KEY})
Comment on lines +87 to +95

Choose a reason for hiding this comment

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

Consider moving this functionality into a helper function increase readability and changeability.

return make_response({"task": task.make_json()})

@tasks_bp.route("/<task_id>/mark_incomplete", methods= ["PATCH"])
def mark_incomplete(task_id):
task = Task.query.get(task_id)

if task == None:
return make_response("", 404)

task.completed_at = None
db.session.commit()
return make_response({"task": task.make_json()})

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

@goals_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({
"id": goal.goal_id,
"title": goal.title,
})
return jsonify(goal_response)

elif request.method == "POST":
request_body = request.get_json()

if "title" not in request_body.keys():
return make_response({"details": "Invalid data"}, 400)

else:
new_goal = Goal(title=request_body["title"])

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

return make_response({"goal": new_goal.create_response()}, 201)

@goals_bp.route("/<goal_id>", methods=["GET", "PUT", "DELETE"])
def handle_goal(goal_id):
goal = Goal.query.get(goal_id)

if goal == None:
return make_response("", 404)

elif request.method == "GET":
return make_response({"goal": goal.create_response()})

elif request.method == "PUT":
request_body = request.get_json()

goal.title = request_body["title"]

db.session.commit()

return make_response({"goal": goal.create_response()})

elif request.method == "DELETE":
db.session.delete(goal)
db.session.commit()

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


@goals_bp.route("/<goal_id>/tasks", methods=["GET", "POST"])
def handle_goal_tasks(goal_id):
goal = Goal.query.get(goal_id)

if goal == None:
return make_response("", 404)

elif request.method == "GET":
return make_response(goal.return_goal_tasks())

elif request.method == "POST":
request_body = request.get_json()
task_ids = request_body["task_ids"]

for id in task_ids:
task = Task.query.get(id)
task.goal_id = goal.goal_id

db.session.commit()

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