Skip to content
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 ada-project-docs/wave_06.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ When I send a `POST` request to `/goals/1/tasks` with this request body:
}
```

Then the three `Task`s belong to the `Goal` and it gets updated in the database, and we get back a `200 OK` with the following response body:
Then the three `Task`s belong to the `Goal`, and it gets updated in the database, and we get back a `200 OK` with the following response body:

```json
{
Expand Down
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
16 changes: 14 additions & 2 deletions app/models/goal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
from flask import current_app
from app import db

from datetime import datetime
from flask import jsonify

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", back_populates="goal")

def get_id(self):
return self.goal_id
def make_dict_response(self, response_code):
response_body = jsonify({"goal" : {
"id": self.get_id(),
"title": self.title,
}}), response_code
Comment on lines +14 to +17

Choose a reason for hiding this comment

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

Style: indentation.

Suggested change
response_body = jsonify({"goal" : {
"id": self.get_id(),
"title": self.title,
}}), response_code
response_body = jsonify({"goal" : {
"id": self.get_id(),
"title": self.title,
}}), response_code

return response_body
32 changes: 31 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
from flask import current_app
from app import db
from datetime import datetime
from flask import jsonify


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, nullable = False)
description = db.Column(db.String, nullable = False)
completed_at = db.Column(db.DateTime, nullable = True)

goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'))
goal = db.relationship("Goal", back_populates="tasks")

def is_complete(self):
return bool(self.completed_at)
def get_id(self):
return self.task_id

def make_dict_response(self, response_code):
# try: response_body = jsonify({"task" : {
# "id": self.get_id(),
# "goal_id": self.goal_id,
# "title": self.title,
# "description": self.description,
# "is_complete": self.is_complete(),
# }})
# except AttributeError:
Comment on lines +22 to +29

Choose a reason for hiding this comment

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

Style: clean up commented out code.

response_body = jsonify({"task" : {
"id": self.get_id(),
"title": self.title,
"description": self.description,
"is_complete": self.is_complete(),
}}), response_code
return response_body
221 changes: 220 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,221 @@
from flask import Blueprint
from app import db
from app.models.task import Task
from app.models.goal import Goal
from flask import Blueprint, jsonify, make_response, request
from sqlalchemy import asc, desc
from datetime import datetime, timezone
import requests
import os

SLACK_POST_MESSAGE_URL = 'https://slack.com/api/chat.postMessage'
SLACK_POST_MESSAGE_CHANNEL = '#task-list'
SLACK_BOT_TOKEN = os.environ.get('SLACK_TOKEN')
SLACK_BOT_USERNAME = 'Waebot'

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

def make_input_valid(number):
try:
int(number)
except:
return make_response(f"{number} is not an int!", 400)

Choose a reason for hiding this comment

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

This is cleaner if you just use abort:

Suggested change
return make_response(f"{number} is not an int!", 400)
abort(make_response(f"{number} is not an int!", 400))

(abort raises a special exception that results in a response being sent back immediately.)


def is_parameter_valid(parameter_id, mdl=Task):
if make_input_valid(parameter_id) is not None:
return make_input_valid(parameter_id)
elif mdl.query.get(parameter_id) is None:
return make_response(f"{parameter_id} is not a valid id!", 404)

def model_select(url):
if "goals" in url:
mdl = Goal
else:
mdl = Task
return mdl
Comment on lines +30 to +35

Choose a reason for hiding this comment

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

This is bordering on too clever but does really DRY your code up!

(By too clever I mean it might be hard to debug/understand.)


@goals_bp.route("",methods=["POST"])
@tasks_bp.route("", methods=["POST"])
def post_tasks():
request_body = request.get_json()
mdl = model_select(request.url)
if mdl == Task:
try: new_obj = Task(title=request_body["title"],
description=request_body["description"],
completed_at=request_body["completed_at"])
except KeyError: return ({"details":"Invalid data"},400)
if mdl == Goal:
try: new_obj = Goal(title=request_body["title"])
except KeyError: return ({"details":"Invalid data"},400)
db.session.add(new_obj)
db.session.commit()
response_body = mdl.make_dict_response(new_obj, 201)
return response_body

@goals_bp.route("<goal_id>/tasks", methods=["POST"])
def create_tasks_with_goals(goal_id):
invalid_param = is_parameter_valid(goal_id, Goal)
if invalid_param:
return make_response(invalid_param)
goal = Goal.query.get(goal_id)

request_body = request.get_json()
task_ids_list = []
try:
for task_id in request_body["task_ids"]:
task = Task.query.get(task_id)
task.goal = goal
task_ids_list.append(task.task_id)
db.session.add(task)
except KeyError: return ({"details":"Invalid data"},400)
db.session.commit()
response_body = {
"id":int(goal_id),
"task_ids": task_ids_list
}
return response_body

@goals_bp.route("/<goal_id>/tasks", methods=["GET"])
def read_goal_tasks(goal_id):
invalid_param = is_parameter_valid(goal_id, Goal)
if invalid_param:
return invalid_param
goal = Goal.query.get(goal_id)
goal_tasks_response = []

for task in goal.tasks:
goal_tasks_response.append(
{
"id": task.task_id,
"goal_id": task.goal_id,
"title": task.title,
"description": task.description,
"is_complete": task.is_complete(),
})

return (jsonify({"id": int(goal_id), "title": goal.title, "tasks": goal_tasks_response}), 200)

@goals_bp.route("/<goal_id>",methods=["PUT"])
@tasks_bp.route("/<task_id>", methods=["PUT"])
def put_tasks(task_id=None, goal_id=None):
if task_id:
obj_id = task_id
if goal_id:
obj_id = goal_id
mdl = model_select(request.url)
invalid_param = is_parameter_valid(obj_id, mdl)
if invalid_param:
return make_response(invalid_param)
model = mdl.query.get(obj_id)
form_data = request.get_json()
if form_data.get("title"):
model.title = form_data["title"]
if form_data.get("description"):
model.description = form_data["description"]
if form_data.get("completed_at"):
model.completed_at = form_data["completed_at"]
db.session.commit()
response_body = mdl.make_dict_response(model, 200)
return response_body

@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def mark_parameter_complete(task_id):
invalid_param = is_parameter_valid(task_id, Task)
if invalid_param:
return make_response(invalid_param)
text = "Someone just completed the task My Beautiful Task"

response = requests.post(SLACK_POST_MESSAGE_URL, {
'token': SLACK_BOT_TOKEN,
'channel': SLACK_POST_MESSAGE_CHANNEL,
'text': text,
'username': SLACK_BOT_USERNAME,})

task = Task.query.get(task_id)
task.completed_at = datetime.now(timezone.utc)
db.session.commit()
print(response.text)

Choose a reason for hiding this comment

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

Style: clean up debugging print calls.

return task.make_dict_response(200)

@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def mark_parameter_incomplete(task_id):
invalid_param = is_parameter_valid(task_id, Task)
if invalid_param:
return make_response(invalid_param)
task = Task.query.get(task_id)
task.completed_at = None
db.session.commit()
return task.make_dict_response(200)

@goals_bp.route("",methods=["GET"])
@tasks_bp.route("",methods=["GET"])
def get_all_tasks_or_goals():
mdl = model_select(request.url)
sort_query = request.args.get("sort")
if sort_query:
if "asc" in sort_query:
models = mdl.query.order_by(mdl.title).all()
if "desc" in sort_query:
models = mdl.query.order_by(mdl.title.desc()).all()
else:
models = mdl.query.all()
models_response = []

for mdl in models:
try:
models_response.append({
"id": mdl.get_id(),
"title": mdl.title,
"description": mdl.description,
"is_complete": mdl.is_complete(),
})
except AttributeError:
models_response.append({
"id": mdl.get_id(),
"title": mdl.title,
})
return (jsonify(models_response), 200)

@goals_bp.route("/<goal_id>", methods=["GET"])
@tasks_bp.route("/<task_id>", methods=["GET"])
def get_one_task_or_goal(task_id=None, goal_id=None):
if task_id:
invalid_param = is_parameter_valid(task_id, Task)
if invalid_param:
return make_response(invalid_param)
obj = Task.query.get(task_id)
if obj.goal_id:
return {"task":{
"id": obj.task_id,
"goal_id": obj.goal_id,
"title": obj.title,
"description": obj.description,
"is_complete": obj.is_complete(),
}}

if goal_id:
invalid_param = is_parameter_valid(goal_id, Goal)
if invalid_param:
return invalid_param
obj = Goal.query.get(goal_id)
return obj.make_dict_response(200)

@goals_bp.route("/<goal_id>",methods=["DELETE"])
@tasks_bp.route("/<task_id>", methods=["DELETE"])
def delete_task(task_id=None, goal_id=None):
if task_id:
obj_id = task_id
obj_str = "Task"
if goal_id:
obj_id = goal_id
obj_str = "Goal"
mdl = model_select(request.url)
invalid_param = is_parameter_valid(obj_id, mdl)
if invalid_param:
return invalid_param
obj = mdl.query.get(obj_id)
db.session.delete(obj)
db.session.commit()

response_body = ({'details' : f'{obj_str} {obj.get_id()} "{obj.title}" successfully deleted'})
return make_response(response_body, 200)
1 change: 1 addition & 0 deletions migrations.old/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.old/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