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

cedar mac #78

Open
wants to merge 6 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: 7 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,24 @@ def create_app(test_config=None):

if test_config is None:
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_DATABASE_URI")
"SQLALCHEMY_DATABASE_URI"
)
else:
app.config["TESTING"] = True
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_TEST_DATABASE_URI")
"SQLALCHEMY_TEST_DATABASE_URI"
)

# Import models here for Alembic setup
from app.models.task import Task
from app.models.goal import Goal
from .routes import tasks_bp, goals_bp

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

# Register Blueprints here
app.register_blueprint(tasks_bp)
app.register_blueprint(goals_bp)

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


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

def to_dict(self, has_tasks=False):
response = {
"id": self.id,
"title": self.title,
}

if has_tasks:
response["tasks"] = [task.to_dict() for task in self.tasks]

return response

def update(self, request_body):

Choose a reason for hiding this comment

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

Nice work creating this DRY, flexible helper method!

for key, value in request_body.items():
if key in Goal.__table__.columns.keys():
setattr(self, key, value)
27 changes: 26 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,29 @@


class Task(db.Model):
task_id = db.Column(db.Integer, primary_key=True)
id = db.Column(db.Integer, 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.id"))
goal = db.relationship("Goal", back_populates="tasks")

def to_dict(self):
is_complete = False if not self.completed_at else True

response = {
"id": self.id,
"title": self.title,
"description": self.description,
"is_complete": is_complete,
}

if self.goal:
response["goal_id"] = self.goal_id
return response

def update(self, request_body):
for key, value in request_body.items():
if key in Task.__table__.columns.keys():
setattr(self, key, value)
182 changes: 181 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,182 @@
from flask import Blueprint
"""
Something went very, very wrong and I had to scrap my original repository,
start over and copy paste my code in. Hence, the lack of commits.
I was beginning the deployment stage and accidently deleted the origin remote.
This lead me down a very dark path, trying different git commands that I didnt fully understand.
It was chaos.

Anyways,
I didn't have time to do docstrings.
Also, I experimented with doubling up the route decorators since the functions were
pretty much the same for Task & Goals.
Not sure if this is a no no in real life, or if there is a better way to do it. Let me know!
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.

This looks good to me! Nice use of before_request

"""


from flask import Blueprint, jsonify, request, abort, g
from app import db
from app.models.task import Task
from app.models.goal import Goal
from datetime import datetime
import requests
import os

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


def slack_bot(text):

Choose a reason for hiding this comment

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

Nice work creating a helper function. You might consider refactoring as an instance method on task given that the text has information about a specific task.


SLACK_KEY = os.environ.get("SLACK_KEY")
req_body = {"channel": "task-notifications", "text": text}
headers = {"Authorization": f"Bearer {SLACK_KEY}"}
path = "https://slack.com/api/chat.postMessage"

requests.post(path, json=req_body, headers=headers)


def validate(model, id):

try:
id = int(id)
except ValueError:
abort(400)
return model.query.get_or_404(id)


def sort(model):

Choose a reason for hiding this comment

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

Nice work breaking this out into a helper function


sort = request.args.get("sort")
if sort == "asc":
model = model.query.order_by(model.title.asc())
elif sort == "desc":
model = model.query.order_by(model.title.desc())

return model


@goals_bp.before_request
@tasks_bp.before_request
def get_model():

bps = {"tasks": (Task, "task"), "goals": (Goal, "goal")}
g.model, g.name = bps[request.blueprint]


@goals_bp.route("", methods=["GET"])
@tasks_bp.route("", methods=["GET"])
def get_all():

model = g.model
if "sort" in request.args:
models = sort(model)
else:
models = model.query.all()

return jsonify([model.to_dict() for model in models])


@goals_bp.route("/<id>", methods=["GET"])
@tasks_bp.route("/<id>", methods=["GET"])
def get_one(id):

model, name = g.model, g.name
model = validate(model, id)

return {f"{name}": model.to_dict()}


@goals_bp.route("", methods=["POST"])
@tasks_bp.route("", methods=["POST"])
def create():

model, name = g.model, g.name
request_body = request.get_json()

try:

Choose a reason for hiding this comment

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

This works, and is very concise! You might consider refactor to check for the specific missing column information to provide these details to the user.

if model == Task:
new_entry = model(
title=request_body["title"],
description=request_body["description"],
completed_at=request_body["completed_at"],
)
elif model == Goal:
new_entry = model(title=request_body["title"])

except:
return {"details": "Invalid data"}, 400

db.session.add(new_entry)
db.session.commit()

return {f"{name}": new_entry.to_dict()}, 201


@goals_bp.route("/<id>", methods=["PUT"])
@tasks_bp.route("/<id>", methods=["PUT"])
def update_one(id):
model, name = g.model, g.name
model = validate(model, id)
request_body = request.get_json()

model.update(request_body)

db.session.commit()
return {f"{name}": model.to_dict()}


@goals_bp.route("/<id>", methods=["DELETE"])
@tasks_bp.route("/<id>", methods=["DELETE"])
def delete_one(id):

model, name = g.model, g.name
model = validate(model, id)

db.session.delete(model)
db.session.commit()

return {
"details": f'{name.capitalize()} {model.id} "{model.title}" successfully deleted'
}


@tasks_bp.route("/<id>/mark_complete", methods=["PATCH"])
def mark_task_complete(id):

task = validate(Task, id)
text = f"Someone just completed the task {task.title}"
slack_bot(text)

task.completed_at = datetime.now()
db.session.commit()

return {"task": task.to_dict()}


@tasks_bp.route("/<id>/mark_incomplete", methods=["PATCH"])
def mark_task_incomplete(id):

task = validate(Task, id)
task.completed_at = None
db.session.commit()

return {"task": task.to_dict()}


@goals_bp.route("/<goal_id>/tasks", methods=["POST"])
def add_tasks_to_goal(goal_id):
goal = validate(Goal, goal_id)
request_body = request.get_json()

try:
goal.tasks = [validate(Task, task_id) for task_id in request_body["task_ids"]]

Choose a reason for hiding this comment

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

Nice use of validate within this list comprehension for clear and concise code.

except:
return {"details": "Invalid data"}, 400

return {"id": goal.id, "task_ids": [task.id for task in goal.tasks]}


@goals_bp.route("/<goal_id>/tasks", methods=["GET"])
def get_tasks_by_goal(goal_id):
goal = validate(Goal, goal_id)
return goal.to_dict(has_tasks=True)

Choose a reason for hiding this comment

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

Cool use of a keyword argument

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