-
Notifications
You must be signed in to change notification settings - Fork 146
ivy-task-list-api #133
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
base: master
Are you sure you want to change the base?
ivy-task-list-api #133
Changes from all commits
b41197d
bed1cf2
c98b41b
4d24c10
4a66f09
2819eb5
2ecdc37
5169e25
3a83758
14144ef
d92bdb2
6c00ab6
30211b1
e99db17
f1ab9b2
031944b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
web: gunicorn 'app:create_app()' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
from app import db | ||
from .models.task import Task | ||
from .models.goal import Goal | ||
from flask import Blueprint, request, make_response, jsonify, abort | ||
import sqlalchemy | ||
from .route_helpers import validate_model_id | ||
import random | ||
|
||
|
||
|
||
goal_bp = Blueprint("goal_bp", __name__, url_prefix="/goals") | ||
|
||
# all goal methods | ||
# Create | ||
@goal_bp.route("", methods=["POST"]) | ||
def create_goal(): | ||
|
||
request_body = request.get_json() | ||
new_goal = Goal.from_dict(request_body) | ||
|
||
db.session.add(new_goal) | ||
db.session.commit() | ||
|
||
return make_response({"goal" : new_goal.to_dict()}, 201) | ||
|
||
# Read | ||
@goal_bp.route("", methods=["GET"]) | ||
def get_all_goals(): | ||
|
||
sort_query = request.args.get("sort") | ||
if sort_query: | ||
sort_function = getattr(sqlalchemy, sort_query) | ||
goal_list = Goal.query.order_by(sort_function(Goal.title)) | ||
else: | ||
goal_list = Goal.query.all() | ||
|
||
response = [] | ||
for goal in goal_list: | ||
response.append(goal.to_dict()) | ||
|
||
return jsonify(response), 200 | ||
|
||
@goal_bp.route("random", methods=["GET"]) | ||
def get_random_goal(): | ||
Comment on lines
+43
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting route! |
||
goal_list = Goal.query.all() | ||
max_index = len(goal_list) - 1 | ||
rand_goal = goal_list[random.randint(0,max_index)] | ||
|
||
return jsonify({"goal" : rand_goal.to_dict()}), 200 | ||
|
||
|
||
|
||
# Individual goal methods | ||
|
||
# Read | ||
@goal_bp.route("/<goal_id>", methods=["GET"]) | ||
def get_specific_goal(goal_id): | ||
|
||
goal = validate_model_id(Goal, goal_id) | ||
|
||
return {"goal" : goal.to_dict()}, 200 | ||
|
||
# Update | ||
@goal_bp.route ("/<goal_id>", methods=["PUT"]) | ||
def update_goal(goal_id): | ||
goal = validate_model_id(Goal, goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
goal.title = request_body["title"] | ||
|
||
db.session.commit() | ||
|
||
return {"goal" : goal.to_dict()}, 200 | ||
|
||
@goal_bp.route("<goal_id>/<marker>", methods=["PATCH"]) | ||
def mark_goal_status(goal_id, marker): | ||
goal = validate_model_id(Goal, goal_id) | ||
eval("goal." + marker + "()") | ||
|
||
db.session.commit() | ||
|
||
return {"goal" : goal.to_dict()}, 200 | ||
|
||
# Delete | ||
@goal_bp.route("/<goal_id>", methods=["DELETE"]) | ||
def delete_goal(goal_id): | ||
goal = validate_model_id(Goal, goal_id) | ||
|
||
db.session.delete(goal) | ||
db.session.commit() | ||
|
||
return make_response({"details" : f"Goal {goal_id} \"{goal.title}\" successfully deleted"}, 200) | ||
|
||
|
||
# Nested route for task assigned to one goal | ||
|
||
@goal_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
def post_task_ids_to_goal(goal_id): | ||
goal = validate_model_id(Goal, goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
|
||
for task_id in request_body["task_ids"]: | ||
new_task = validate_model_id(Task, task_id) | ||
new_task.goal_id = goal_id | ||
|
||
db.session.commit() | ||
|
||
return make_response({ | ||
"id" : goal.id, | ||
"task_ids" : goal.get_task_ids() | ||
}, 200) | ||
|
||
@goal_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
def get_tasks_from_goal(goal_id): | ||
goal = validate_model_id(Goal, goal_id) | ||
response_body = goal.to_dict() | ||
response_body.update({"tasks" : goal.get_tasks()}) | ||
|
||
return make_response(response_body, 200) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,39 @@ | ||
from app import db | ||
from flask import abort, make_response | ||
|
||
|
||
class Goal(db.Model): | ||
goal_id = db.Column(db.Integer, primary_key=True) | ||
id = db.Column(db.Integer, primary_key=True, autoincrement=True) | ||
title = db.Column(db.String) | ||
tasks = db.relationship("Task", back_populates="goal") | ||
|
||
def to_dict(self): | ||
goal_dict = { | ||
"id" : self.id, | ||
"title" : self.title, | ||
} | ||
return goal_dict | ||
|
||
def get_tasks(self): | ||
response = [] | ||
for task in self.tasks: | ||
response.append(task.to_dict()) | ||
return response | ||
|
||
def get_task_ids(self): | ||
response = [] | ||
for task in self.tasks: | ||
response.append(task.id) | ||
return response | ||
|
||
|
||
|
||
@classmethod | ||
def from_dict(cls, request_body): | ||
Comment on lines
+31
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great helper method |
||
try: | ||
goal = Goal(title = request_body["title"]) | ||
return goal | ||
except: | ||
abort(make_response({"details" : "Invalid data"}, 400)) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,52 @@ | ||
from app import db | ||
from flask import abort, make_response | ||
import datetime | ||
|
||
|
||
class Task(db.Model): | ||
task_id = db.Column(db.Integer, primary_key=True) | ||
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 = db.relationship("Goal", back_populates="tasks") | ||
goal_id = db.Column(db.Integer, db.ForeignKey('goal.id'), nullable = True) | ||
|
||
|
||
|
||
def is_complete(self): | ||
if self.completed_at == None: | ||
return False | ||
else: | ||
return True | ||
|
||
def to_dict(self): | ||
task_dict = { | ||
"id" :self.id, | ||
"title" : self.title, | ||
"description" : self.description, | ||
"is_complete" : self.is_complete() | ||
} | ||
if self.goal_id != None: | ||
task_dict.update({"goal_id" : self.goal_id}) | ||
return task_dict | ||
|
||
def mark_complete(self): | ||
if self.is_complete(): | ||
pass | ||
else: | ||
self.completed_at = datetime.datetime.now() | ||
|
||
def mark_incomplete(self): | ||
if not self.is_complete(): | ||
pass | ||
else: | ||
self.completed_at = None | ||
|
||
@classmethod | ||
def from_dict(cls, request_body): | ||
try: | ||
task = Task(title = request_body["title"], | ||
description = request_body["description"]) | ||
return task | ||
except: | ||
abort(make_response({"details" : "Invalid data"}, 400)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
|
||
from flask import make_response, abort | ||
|
||
|
||
|
||
def validate_model_id(cls, id): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice helper function. It would be good to have another to validate required fields in the request body. |
||
cls_name = cls.__name__ | ||
|
||
try: | ||
id = int(id) | ||
except: | ||
abort(make_response({"message" : f"{cls_name.lower()} id: {id} is invalid"}, 400)) | ||
|
||
model = cls.query.get(id) | ||
|
||
if not model: | ||
abort(make_response({"message" : f"{cls_name.lower()} {id} not found"}, 404)) | ||
|
||
return model |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
from app import db | ||
from .models.task import Task | ||
from .models.goal import Goal | ||
from flask import Blueprint, request, make_response, jsonify, abort | ||
import sqlalchemy | ||
from .route_helpers import validate_model_id | ||
|
||
|
||
|
||
task_bp = Blueprint("task_bp", __name__, url_prefix="/tasks") | ||
|
||
|
||
# all task methods | ||
# Create | ||
@task_bp.route("", methods=["POST"]) | ||
def create_task(): | ||
|
||
request_body = request.get_json() | ||
new_task = Task.from_dict(request_body) | ||
Comment on lines
+18
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There doesn't seem to be any validation to check to see if the request body has a title or description. |
||
|
||
db.session.add(new_task) | ||
db.session.commit() | ||
|
||
return make_response({"task" : new_task.to_dict()}, 201) | ||
|
||
# Read | ||
@task_bp.route("", methods=["GET"]) | ||
def get_all_task(): | ||
|
||
sort_query = request.args.get("sort") | ||
if sort_query: | ||
sort_function = getattr(sqlalchemy, sort_query) | ||
task_list = Task.query.order_by(sort_function(Task.title)) | ||
else: | ||
task_list = Task.query.all() | ||
|
||
response = [] | ||
for task in task_list: | ||
response.append(task.to_dict()) | ||
|
||
return jsonify(response), 200 | ||
|
||
|
||
|
||
# Individual task methods | ||
|
||
# Read | ||
@task_bp.route("/<task_id>", methods=["GET"]) | ||
def get_one_task(task_id): | ||
|
||
task = validate_model_id(Task, task_id) | ||
|
||
return {"task" : task.to_dict()}, 200 | ||
|
||
# Update | ||
@task_bp.route ("/<task_id>", methods=["PUT"]) | ||
def update_task(task_id): | ||
task = validate_model_id(Task, task_id) | ||
|
||
request_body = request.get_json() | ||
|
||
task.title = request_body["title"] | ||
task.description = request_body["description"] | ||
Comment on lines
+62
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No data validation here? |
||
|
||
db.session.commit() | ||
|
||
return {"task" : task.to_dict()}, 200 | ||
|
||
@task_bp.route("<task_id>/<marker>", methods=["PATCH"]) | ||
def mark_task_status(task_id, marker): | ||
task = validate_model_id(Task, task_id) | ||
eval("task." + marker + "()") | ||
|
||
db.session.commit() | ||
|
||
return {"task" : task.to_dict()}, 200 | ||
|
||
# Delete | ||
@task_bp.route("/<task_id>", methods=["DELETE"]) | ||
def delete_task(task_id): | ||
task = validate_model_id(Task, task_id) | ||
|
||
db.session.delete(task) | ||
db.session.commit() | ||
|
||
return make_response({"details" : f"Task {task_id} \"{task.title}\" successfully deleted"}, 200) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Generic single-database configuration. |
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clever, nice!