-
Notifications
You must be signed in to change notification settings - Fork 146
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
Lion - Miranda #123
base: master
Are you sure you want to change the base?
Lion - Miranda #123
Changes from all commits
3224d54
76048c3
f4ab1a6
c087765
1ec4112
4b34d84
4371548
d482d77
98fb423
c945e3b
2254dd6
2b515b4
2dc97b3
31410f6
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,97 @@ | ||
|
||
from flask import Blueprint, request, make_response, jsonify | ||
from app import db | ||
from app.models.task import Task | ||
from app.models.goal import Goal | ||
from app.routes_helper import get_one_obj_or_abort | ||
|
||
goal_bp = Blueprint("goal_bp", __name__, url_prefix="/goals") | ||
|
||
|
||
# create goal | ||
@goal_bp.route("", methods=["POST"]) | ||
def create_goal(): | ||
response_body = request.get_json() | ||
|
||
if "title" not in response_body: | ||
return jsonify({"details": "Invalid data"}), 400 | ||
|
||
new_goal = Goal(title = response_body["title"]) | ||
|
||
db.session.add(new_goal) | ||
db.session.commit() | ||
return jsonify({"goal":new_goal.return_body()}), 201 | ||
|
||
|
||
# Get Goals | ||
@goal_bp.route("", methods=["GET"]) | ||
def read_goal(): | ||
goals = Goal.query.all() | ||
response = [goal.return_body() for goal in goals] | ||
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 use of a list comprehension. |
||
return jsonify(response), 200 | ||
|
||
|
||
# Get One Goal | ||
@goal_bp.route("/<goal_id>", methods=["GET"]) | ||
def get_one_goal_by_id(goal_id): | ||
chosen_goal= get_one_obj_or_abort(Goal, goal_id) | ||
return jsonify({"goal":chosen_goal.return_body()}), 200 | ||
|
||
|
||
# Update Goal | ||
@goal_bp.route("/<goal_id>", methods=["PUT"]) | ||
def update_goal(goal_id): | ||
chosen_goal = get_one_obj_or_abort(Goal, goal_id) | ||
request_body = request.get_json() | ||
|
||
chosen_goal.title = request_body["title"] | ||
|
||
db.session.commit() | ||
return jsonify({"goal":chosen_goal.return_body()}), 200 | ||
|
||
|
||
# Delete Goal | ||
@goal_bp.route("/<goal_id>", methods=["DELETE"]) | ||
def delete_goal_by_id(goal_id): | ||
chosen_goal = get_one_obj_or_abort(Goal, goal_id) | ||
|
||
db.session.delete(chosen_goal) | ||
db.session.commit() | ||
|
||
return jsonify({"details": f'Goal {chosen_goal.goal_id} "{chosen_goal.title}" successfully deleted'}), 200 | ||
|
||
|
||
|
||
|
||
# ================================================== | ||
# One-to-Many Relationship bewteen goals and tasks | ||
# ================================================== | ||
|
||
@goal_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
def post_task_belonging_to_a_goal(goal_id): | ||
parent_goal = get_one_obj_or_abort(Goal, goal_id) | ||
request_body = request.get_json() | ||
|
||
for task in request_body["task_ids"]: | ||
chosen_task = get_one_obj_or_abort(Task, task) | ||
chosen_task.goal = parent_goal | ||
|
||
db.session.add(chosen_task) | ||
db.session.commit() | ||
|
||
return jsonify({"id": int(goal_id), "task_ids": request_body["task_ids"]}), 200 | ||
|
||
|
||
|
||
@goal_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
def get_task_belonging_to_a_goal(goal_id): | ||
parent_goal = get_one_obj_or_abort(Goal, goal_id) | ||
|
||
task_list = [] | ||
for task in parent_goal.tasks: | ||
task_list.append(task.return_body()) | ||
|
||
response_dict = parent_goal.return_body() | ||
response_dict["tasks"] = task_list | ||
return jsonify(response_dict), 200 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,23 @@ | ||
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", back_populates="goal", lazy=True) | ||
|
||
|
||
def return_body(self): | ||
return { | ||
"id": self.goal_id, | ||
"title": self.title | ||
} | ||
|
||
@classmethod | ||
def from_dict(cls, data_dict): | ||
if "title" in data_dict: | ||
new_obj = cls(title=data_dict["title"]) | ||
return new_obj | ||
|
||
Comment on lines
+17
to
+22
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. Good use of a class-method as a helper. |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,48 @@ | ||
from app import db | ||
|
||
|
||
# create the table with attributes | ||
# after created, flask db migrate, flask db upgrade | ||
class Task(db.Model): | ||
task_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.goal_id'),nullable=True) | ||
goal = db.relationship("Goal", back_populates="tasks") | ||
|
||
|
||
def return_body(self): | ||
task_dict = { | ||
"id": self.task_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": self.check_complete_or_not() | ||
} | ||
if self.goal_id: | ||
task_dict["goal_id"] = self.goal_id | ||
|
||
return task_dict | ||
|
||
|
||
@classmethod | ||
def from_dict(cls, data_dict): | ||
if "title" in data_dict and\ | ||
"description" in data_dict and\ | ||
"completed_at" in data_dict: | ||
new_obj = cls( | ||
title=data_dict["title"], | ||
description=data_dict["description"], | ||
completed_at=data_dict["completed_at"] | ||
) | ||
return new_obj | ||
|
||
|
||
def check_complete_or_not(self): | ||
if self.completed_at: | ||
is_complete = True | ||
else: | ||
is_complete = False | ||
return is_complete | ||
|
||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from flask import jsonify, abort, make_response | ||
|
||
def get_one_obj_or_abort(cls, obj_id): | ||
try: | ||
obj_id = int(obj_id) | ||
except ValueError: | ||
response_str = f"Invalid ID: '{obj_id}' must be an integer" | ||
abort(make_response(jsonify({"message":response_str}), 400)) | ||
|
||
matching_obj = cls.query.get(obj_id) | ||
|
||
if not matching_obj: | ||
response_str = f"ID: '{cls.__name__}' was not found in the database" | ||
abort(make_response(jsonify({"message":response_str}), 404)) | ||
|
||
return matching_obj |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import requests, os | ||
from datetime import datetime | ||
from flask import Blueprint, request, make_response, jsonify | ||
from app import db | ||
from app.models.task import Task | ||
from app.routes_helper import get_one_obj_or_abort | ||
|
||
|
||
task_bp = Blueprint("task_bp", __name__, url_prefix="/tasks") | ||
goal_bp = Blueprint("goal_bp", __name__, url_prefix="/goals") | ||
|
||
|
||
# Create a Task: Valid Task With null completed_at | ||
@task_bp.route("", methods=["POST"]) | ||
def create_task(): | ||
response_body = request.get_json() | ||
|
||
if "title" not in response_body or\ | ||
"description" not in response_body or\ | ||
"completed_at" not in response_body: | ||
return jsonify({"details": "Invalid data"}), 400 | ||
|
||
new_task = Task.from_dict(response_body) | ||
|
||
db.session.add(new_task) | ||
db.session.commit() | ||
|
||
# using the class method in task.py | ||
return jsonify({"task":new_task.return_body()}), 201 | ||
|
||
|
||
# Get Tasks: Getting Saved Tasks, sorting by ascending/descending | ||
@task_bp.route("", methods=["GET"]) | ||
def read_task(): | ||
sort_query = request.args.get("sort") | ||
|
||
if sort_query == "asc": | ||
tasks = Task.query.order_by(Task.title.asc()) | ||
elif sort_query == "desc": | ||
tasks = Task.query.order_by(Task.title.desc()) | ||
else: | ||
tasks = Task.query.all() | ||
|
||
response = [task.return_body() for task in tasks] | ||
return jsonify(response), 200 | ||
|
||
|
||
|
||
# Get One Task: One Saved Task | ||
@task_bp.route("/<task_id>", methods=["GET"]) | ||
def get_one_task_by_id(task_id): | ||
chosen_task = get_one_obj_or_abort(Task, task_id) | ||
|
||
return jsonify({"task":chosen_task.return_body()}), 200 | ||
|
||
|
||
# Update Task | ||
@task_bp.route("/<task_id>", methods=["PUT"]) | ||
def update_task(task_id): | ||
chosen_task = get_one_obj_or_abort(Task, task_id) | ||
request_body = request.get_json() | ||
chosen_task.title = request_body["title"] | ||
chosen_task.description = request_body["description"] | ||
|
||
db.session.commit() | ||
return jsonify({"task":chosen_task.return_body()}), 200 | ||
|
||
|
||
|
||
# Deleting a Task | ||
@task_bp.route("/<task_id>", methods=["DELETE"]) | ||
def delete_one_task(task_id): | ||
task_to_delete = get_one_obj_or_abort(Task, task_id) | ||
|
||
db.session.delete(task_to_delete) | ||
db.session.commit() | ||
|
||
return jsonify({"details": f'Task {task_to_delete.task_id} "{task_to_delete.title}" successfully deleted'}), 200 | ||
|
||
|
||
@task_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||
def mark_complete_update(task_id): | ||
chosen_task = get_one_obj_or_abort(Task, task_id) | ||
task = Task.query.get(task_id) | ||
if task is None: | ||
return make_response("The task was not found", 404) | ||
task.completed_at = datetime.now() | ||
db.session.commit() | ||
|
||
PATH = "https://slack.com/api/chat.postMessage" | ||
|
||
SLACKBOT_TOKEN = os.environ.get("SLACKBOT_TOKEN") | ||
|
||
# the query parameters come from the | ||
query_params = { | ||
"token": SLACKBOT_TOKEN, | ||
"channel": "task-notifications", | ||
"text": f"Someone just completed the task {task.title}" | ||
} | ||
|
||
requests.post(url=PATH, data=query_params, headers={"Authorization": SLACKBOT_TOKEN}) | ||
Comment on lines
+90
to
+101
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. The slack portion of this method might do well as a helper function. |
||
# POST: to submit data to be processed to the server. | ||
|
||
return jsonify({"task":chosen_task.return_body()}), 200 | ||
|
||
|
||
@task_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||
def mark_incomplete_update(task_id): | ||
chosen_task = get_one_obj_or_abort(Task, task_id) | ||
task = Task.query.get(task_id) | ||
if task is None: | ||
return make_response("The task was not found", 404) | ||
task.completed_at = None | ||
db.session.commit() | ||
return jsonify({"task":chosen_task.return_body()}), 200 | ||
|
||
|
||
# helper function to check the value of completed_at | ||
# def check_task_status(goal_id, result): | ||
# chosen_task = validate_task(goal_id) | ||
# task = Task.query.get(goal_id) | ||
# if task is None: | ||
# return make_response("The task was not found", 404) | ||
# task.complete_at = result | ||
# db.session.commit() | ||
# return jsonify({"task":chosen_task.return_body()}), 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.
It's good that you're doing data validation, but you should probably indicate what data is invalid.