-
Notifications
You must be signed in to change notification settings - Fork 97
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 - Jessica - Task List #74
base: master
Are you sure you want to change the base?
Changes from all commits
ed23c08
425ae71
11fd4e7
c3b6c8e
a5c7820
387f540
6dd7de0
709f945
787f870
f3001eb
69c07ca
1c00ed9
e33e799
ebf73ea
21786bd
4f826f4
5b2e592
c5cbd1d
b2ba54a
cbb8dd8
2344341
13765c5
3fe2824
aedee15
538cfdb
d6a1116
e5c9ea3
254f2b5
715c9ef
77f5dc6
bed7ae4
984bbd1
7dfadb0
763f162
3b29755
c4adf6f
dc4813a
4aca36c
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 |
---|---|---|
@@ -1,6 +1,30 @@ | ||
from re import T | ||
from flask import current_app | ||
from flask.helpers import make_response | ||
from sqlalchemy.orm import backref, lazyload | ||
from app import db | ||
|
||
|
||
class Goal(db.Model): | ||
goal_id = db.Column(db.Integer, primary_key=True) | ||
goal_id = db.Column(db.Integer, primary_key=True, autoincrement=True) | ||
title = db.Column(db.String, nullable=False) | ||
tasks = db.relationship("Task", backref="goal", lazy=True) | ||
|
||
def to_dict(self): | ||
return { | ||
"id": self.goal_id, | ||
"title": self.title | ||
} | ||
|
||
def create_task_list(self): | ||
task_list = [] | ||
for task in self.tasks: | ||
task_list.append(task.to_dict()) | ||
return task_list | ||
|
||
def to_dict_with_tasks(self): | ||
return { | ||
"id": self.goal_id, | ||
"title": self.title, | ||
"tasks": [task.to_dict() for task in self.tasks] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,31 @@ | ||
from flask import current_app | ||
from sqlalchemy.orm import lazyload | ||
from app import db | ||
from app.models.goal import Goal | ||
|
||
|
||
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, 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), nullable=True) | ||
|
||
def to_dict(self): | ||
complete = False | ||
if self.completed_at: | ||
complete = True | ||
if self.goal_id: | ||
return { | ||
"id": self.task_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": complete, | ||
"goal_id": self.goal_id | ||
} | ||
return { | ||
"id": self.task_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": complete | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,95 @@ | ||||||||||||||||||||||
from flask import Blueprint, jsonify, make_response, request, abort | ||||||||||||||||||||||
from flask.helpers import make_response | ||||||||||||||||||||||
from flask.json import tojson_filter | ||||||||||||||||||||||
from flask.signals import request_tearing_down | ||||||||||||||||||||||
from werkzeug.utils import header_property | ||||||||||||||||||||||
from app.models.goal import Goal | ||||||||||||||||||||||
from app import db | ||||||||||||||||||||||
from datetime import datetime | ||||||||||||||||||||||
from app.routes.utils import valid_int | ||||||||||||||||||||||
from app.models.task import Task | ||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
goal_bp = Blueprint("goal", __name__,url_prefix ="/goals") | ||||||||||||||||||||||
|
||||||||||||||||||||||
# Helper Functions | ||||||||||||||||||||||
def get_goal_from_id(goal_id): | ||||||||||||||||||||||
valid_int(goal_id, "goal_id") | ||||||||||||||||||||||
return Goal.query.get_or_404(goal_id, description="{goal not found}") | ||||||||||||||||||||||
Comment on lines
+16
to
+18
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. Since
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
# Routes | ||||||||||||||||||||||
@goal_bp.route("", methods=["POST"]) | ||||||||||||||||||||||
def create_goal(): | ||||||||||||||||||||||
request_body = request.get_json() | ||||||||||||||||||||||
if "title" not in request_body: | ||||||||||||||||||||||
return make_response({"details": "Invalid data"}, 400) | ||||||||||||||||||||||
|
||||||||||||||||||||||
new_goal = Goal( | ||||||||||||||||||||||
title=request_body["title"] | ||||||||||||||||||||||
) | ||||||||||||||||||||||
|
||||||||||||||||||||||
db.session.add(new_goal) | ||||||||||||||||||||||
db.session.commit() | ||||||||||||||||||||||
|
||||||||||||||||||||||
return make_response({"goal": new_goal.to_dict()}, 201) | ||||||||||||||||||||||
|
||||||||||||||||||||||
@goal_bp.route("", methods=["GET"]) | ||||||||||||||||||||||
def read_all_goals(): | ||||||||||||||||||||||
|
||||||||||||||||||||||
sort_query = request.args.get("sort") | ||||||||||||||||||||||
|
||||||||||||||||||||||
if sort_query == "asc": | ||||||||||||||||||||||
goals = Goal.query.order_by(Goal.title.asc()) | ||||||||||||||||||||||
elif sort_query == "desc": | ||||||||||||||||||||||
goals = Goal.query.order_by(Goal.title.desc()) | ||||||||||||||||||||||
else: | ||||||||||||||||||||||
goals = Goal.query.all() | ||||||||||||||||||||||
|
||||||||||||||||||||||
goal_response = [] | ||||||||||||||||||||||
for goal in goals: | ||||||||||||||||||||||
goal_response.append( | ||||||||||||||||||||||
goal.to_dict() | ||||||||||||||||||||||
) | ||||||||||||||||||||||
return make_response(jsonify(goal_response), 200) | ||||||||||||||||||||||
|
||||||||||||||||||||||
@goal_bp.route("/<goal_id>", methods=["GET"]) | ||||||||||||||||||||||
def read_one_goal(goal_id): | ||||||||||||||||||||||
goal = get_goal_from_id(goal_id) | ||||||||||||||||||||||
return make_response({"goal": goal.to_dict()}, 200) | ||||||||||||||||||||||
|
||||||||||||||||||||||
@goal_bp.route("/<goal_id>", methods=["PUT"]) | ||||||||||||||||||||||
def update_goal(goal_id): | ||||||||||||||||||||||
goal = get_goal_from_id(goal_id) | ||||||||||||||||||||||
request_body = request.get_json() | ||||||||||||||||||||||
goal.title=request_body["title"] | ||||||||||||||||||||||
db.session.commit() | ||||||||||||||||||||||
return make_response({"goal": goal.to_dict()}, 200) | ||||||||||||||||||||||
|
||||||||||||||||||||||
@goal_bp.route("/<goal_id>", methods=["DELETE"]) | ||||||||||||||||||||||
def delete_goal(goal_id): | ||||||||||||||||||||||
goal = get_goal_from_id(goal_id) | ||||||||||||||||||||||
|
||||||||||||||||||||||
db.session.delete(goal) | ||||||||||||||||||||||
db.session.commit() | ||||||||||||||||||||||
|
||||||||||||||||||||||
return make_response({"details": f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"}, 200) | ||||||||||||||||||||||
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. This is a really good message! Very clear. 😄 |
||||||||||||||||||||||
|
||||||||||||||||||||||
@goal_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||||||||||||||||||||||
def link_tasks_to_goals(goal_id): | ||||||||||||||||||||||
request_body = request.get_json() | ||||||||||||||||||||||
task_ids = request_body["task_ids"] | ||||||||||||||||||||||
for t_id in task_ids: | ||||||||||||||||||||||
task = Task.query.get(t_id) | ||||||||||||||||||||||
task.goal_id = goal_id | ||||||||||||||||||||||
db.session.commit() | ||||||||||||||||||||||
response_body = { | ||||||||||||||||||||||
"id": int(goal_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. Remember, if It may be worth using your |
||||||||||||||||||||||
"task_ids": task_ids | ||||||||||||||||||||||
} | ||||||||||||||||||||||
return make_response(response_body, 200) | ||||||||||||||||||||||
|
||||||||||||||||||||||
@goal_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||||||||||||||||||||||
def read_tasks_for_goal(goal_id): | ||||||||||||||||||||||
goal = get_goal_from_id(goal_id) | ||||||||||||||||||||||
return make_response(goal.to_dict_with_tasks(), 200) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,113 @@ | ||||||
from flask import Blueprint, jsonify, make_response, request, abort | ||||||
from flask.helpers import make_response | ||||||
from flask.json import tojson_filter | ||||||
from flask.signals import request_tearing_down | ||||||
from werkzeug.utils import header_property | ||||||
from app.models.task import Task | ||||||
from app import db | ||||||
from datetime import datetime | ||||||
from dotenv import load_dotenv | ||||||
import requests, os | ||||||
from app.routes.utils import valid_int | ||||||
|
||||||
load_dotenv() | ||||||
|
||||||
task_bp = Blueprint("task", __name__,url_prefix ="/tasks") | ||||||
|
||||||
# Helper Functions | ||||||
def get_task_from_id(task_id): | ||||||
valid_int(task_id, "task_id") | ||||||
return Task.query.get_or_404(task_id, description="{task not found}") | ||||||
|
||||||
def post_slack_message(message): | ||||||
token = os.environ.get('SLACK_TOKEN') | ||||||
CHANNEL_ID = "C02KD4B5A07" | ||||||
|
||||||
Headers = {"Authorization": "Bearer xoxb-" + token} | ||||||
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. This line causes a failure if the It's perfectly reasonable to assume you will have a key (and the tests pass if given the token of If you wanted to fix the test failures you could use interpolation to do this instead:
Suggested change
Again, this isn't a bug, just a quirk of our Learn setup. It's just the kind of thing where I would want to know why it failed on Learn and not on my local machine. |
||||||
data = { | ||||||
"channel": CHANNEL_ID, | ||||||
"text": message | ||||||
} | ||||||
response = requests.post("https://slack.com/api/chat.postMessage", headers=Headers, json=data) | ||||||
return response | ||||||
|
||||||
|
||||||
# Routes | ||||||
@task_bp.route("", methods=["POST"]) | ||||||
def create_task(): | ||||||
request_body = request.get_json() | ||||||
if "title" not in request_body or "description" not in request_body or "completed_at" not in request_body: | ||||||
return make_response({"details": "Invalid data"}, 400) | ||||||
|
||||||
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.to_dict()}, 201) | ||||||
|
||||||
@task_bp.route("", methods=["GET"]) | ||||||
def read_all_tasks(): | ||||||
|
||||||
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() | ||||||
|
||||||
task_response = [] | ||||||
for task in tasks: | ||||||
task_response.append( | ||||||
task.to_dict() | ||||||
) | ||||||
return make_response(jsonify(task_response), 200) | ||||||
|
||||||
@task_bp.route("/<task_id>", methods=["GET"]) | ||||||
def read_one_task(task_id): | ||||||
task = get_task_from_id(task_id) | ||||||
return make_response({"task": task.to_dict()}, 200) | ||||||
|
||||||
@task_bp.route("/<task_id>", methods=["PUT"]) | ||||||
def update_task(task_id): | ||||||
task = get_task_from_id(task_id) | ||||||
request_body = request.get_json() | ||||||
task.title=request_body["title"] | ||||||
task.description=request_body["description"] | ||||||
db.session.commit() | ||||||
return make_response({"task": task.to_dict()}, 200) | ||||||
|
||||||
@task_bp.route("/<task_id>", methods=["DELETE"]) | ||||||
def delete_task(task_id): | ||||||
task = get_task_from_id(task_id) | ||||||
|
||||||
db.session.delete(task) | ||||||
db.session.commit() | ||||||
|
||||||
return make_response({"details": f"Task {task.task_id} \"{task.title}\" successfully deleted"}, 200) | ||||||
|
||||||
@task_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||||||
def mark_task_complete(task_id): | ||||||
task = get_task_from_id(task_id) | ||||||
task.completed_at = datetime.utcnow() | ||||||
|
||||||
db.session.commit() | ||||||
message = f"Someone just completed the task {task.title}" | ||||||
post_slack_message(message) | ||||||
|
||||||
return make_response({"task": task.to_dict()}, 200) | ||||||
|
||||||
@task_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||||||
def mark_task_incomplete(task_id): | ||||||
task = get_task_from_id(task_id) | ||||||
task.completed_at = None | ||||||
db.session.commit() | ||||||
|
||||||
return make_response({"task": task.to_dict()}, 200) | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from flask import make_response, abort | ||
|
||
def valid_int(number, parameter_type): | ||
try: | ||
int(number) | ||
except: | ||
abort(make_response({"error": f"{parameter_type} must be an int"}, 400)) |
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.
You're the second person I've seen import this but I'm not sure what effect it actually has (it doesn't seem explicitly referenced).
I'm curious, why the import?