-
Notifications
You must be signed in to change notification settings - Fork 128
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
task-list-project-paigeh #131
base: main
Are you sure you want to change the base?
Changes from all commits
f56352f
172a546
fe5c5c4
565e13b
4e6ac76
a99e020
42a1154
1f91314
09ceb45
8630035
a950316
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 |
---|---|---|
|
@@ -138,4 +138,6 @@ dmypy.json | |
.pytype/ | ||
|
||
# Cython debug symbols | ||
cython_debug/ | ||
cython_debug/ | ||
|
||
.env |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,20 @@ | ||
from app import db | ||
from sqlalchemy.orm import relationship | ||
|
||
|
||
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, tasks=False): | ||
goal_dict = { | ||
"id": self.id, | ||
"title": self.title | ||
} | ||
|
||
if tasks: | ||
goal_dict["tasks"] = [task.to_dict() for task in self.tasks] | ||
|
||
return goal_dict | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,27 @@ | ||
from app import db | ||
from datetime import datetime | ||
|
||
|
||
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 = db.relationship("Goal", back_populates="tasks") | ||
goal_id = db.Column(db.Integer, db.ForeignKey('goal.id'), nullable=True) | ||
|
||
def to_dict(self): | ||
task_as_dict = { | ||
"id": self.id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": self.completed_at != None | ||
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 choice to derive the value for I want to share a gentle reminder that we should use |
||
} | ||
if self.goal_id: | ||
task_as_dict["goal_id"] = self.goal_id | ||
|
||
return task_as_dict | ||
|
||
# @classmethod | ||
# def from_dict(cls, task_data): | ||
# return cls(title=task_data["title"], description=task_data["description"]) | ||
Comment on lines
+25
to
+27
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. Gentle reminder that we want to clean up commented code before opening PRs - we can look up our past commits on GitHub if we need to take a look at a previous state =] |
This file was deleted.
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. Really nicely divided up routes overall, there a a few long lines, but the use of spacing and naming makes things easy to quickly read and understand =] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
from flask import Blueprint, jsonify, abort, make_response, request | ||
from app.models.goal import Goal | ||
from app.models.task import Task | ||
from app.routes.routes import validate_model | ||
from datetime import datetime | ||
from app import db | ||
|
||
goals_bp = Blueprint("goals", __name__, url_prefix="/goals") | ||
|
||
@goals_bp.route("", methods=["POST"]) | ||
def create_goal(): | ||
request_body = request.get_json() | ||
if "title" not in request_body: | ||
abort(make_response({"details": "Invalid data"}, 400)) | ||
|
||
|
||
new_goal = Goal(title=request_body["title"]) | ||
db.session.add(new_goal) | ||
db.session.commit() | ||
return make_response(jsonify({"goal": new_goal.to_dict()}), 201) | ||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
def create_tasks_for_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
request_body = request.get_json() | ||
# if "tasks_ids" not in request_body: | ||
# abort(make_response({"details": "Invalid data"}, 400)) | ||
|
||
task_ids = request_body["task_ids"] | ||
for task_id in task_ids: | ||
task = validate_model(Task, task_id) | ||
task.goal_id = goal.id | ||
|
||
db.session.commit() | ||
return make_response({"id": goal.id, "task_ids": task_ids}, 200) | ||
|
||
@goals_bp.route("", methods=["GET"]) | ||
def read_all_goals(): | ||
goals = Goal.query.all() | ||
goal_response = [] | ||
|
||
for goal in goals: | ||
goal_response.append(goal.to_dict()) | ||
return jsonify(goal_response) | ||
|
||
@goals_bp.route("/<goal_id>", methods=["GET"]) | ||
def read_one_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
return make_response(jsonify({"goal":goal.to_dict()}), 200) | ||
|
||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
def read_tasks_for_one_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
return make_response(goal.to_dict(tasks=True), 200) | ||
|
||
|
||
|
||
@goals_bp.route("/<goal_id>", methods=["PUT"]) | ||
def update_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
request_body = request.get_json() | ||
|
||
goal.title = request_body["title"] | ||
|
||
db.session.commit() | ||
return make_response({"goal":goal.to_dict()}, 200) | ||
|
||
@goals_bp.route("/<goal_id>", methods=["DELETE"]) | ||
def delete_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
|
||
db.session.delete(goal) | ||
db.session.commit() | ||
|
||
return make_response({"details": 'Goal 1 "Build a habit of going outside daily" 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. How might we split this line up to keep it under 79 characters? What other lines across the project would be nice to split up for readability? |
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 this file mostly holds task routes, I suggest updating the name to reflect that. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
from flask import Blueprint, jsonify, abort, make_response, request | ||
from app.models.task import Task | ||
from datetime import datetime | ||
import os | ||
import requests | ||
from dotenv import load_dotenv | ||
from app import db | ||
|
||
load_dotenv() | ||
SLACK_TOKEN = os.environ.get("SLACK_TOKEN") | ||
tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") | ||
|
||
|
||
def validate_model(cls, model_id): | ||
try: | ||
model_id = int(model_id) | ||
except: | ||
abort(make_response({"message":f"{cls.__name__.lower()} {model_id} invalid data"}, 400)) | ||
|
||
task = cls.query.get(model_id) | ||
|
||
if not task: | ||
abort(make_response({"message":f"{cls.__name__.lower()} {model_id} not found"}, 404)) | ||
|
||
return task | ||
|
||
def send__slack_msg(task_title): | ||
slack_url = "https://slack.com/api/chat.postMessage" | ||
headers = { | ||
"Authorization": f"Bearer {SLACK_TOKEN}" | ||
} | ||
data = { | ||
"channel": "task-notifications", | ||
"text": f"Someone just completed the task {task_title}" | ||
} | ||
|
||
return requests.post(slack_url, headers=headers, data=data) | ||
|
||
|
||
|
||
@tasks_bp.route("", methods=["POST"]) | ||
def create_task(): | ||
request_body = request.get_json() | ||
if "title" not in request_body or "description" not in request_body: | ||
return make_response({"details": "Invalid data"}, 400) | ||
|
||
new_task = Task(title=request_body["title"], | ||
description=request_body["description"], | ||
completed_at=None) | ||
|
||
db.session.add(new_task) | ||
db.session.commit() | ||
|
||
return make_response(jsonify({"task": new_task.to_dict()}), 201) | ||
|
||
|
||
@tasks_bp.route("", methods=["GET"]) | ||
def read_all_tasks(): | ||
# tasks = Task.query.get.all() | ||
sort = request.args.get("sort") | ||
if sort == "asc": | ||
tasks = Task.query.order_by(Task.title.asc()).all() | ||
else: | ||
tasks = Task.query.order_by(Task.title.desc()).all() | ||
tasks_response = [] | ||
|
||
for task in tasks: | ||
tasks_response.append(task.to_dict()) | ||
return jsonify(tasks_response) | ||
|
||
@tasks_bp.route("/<task_id>", methods=["GET"]) | ||
def read_one_task(task_id): | ||
task = validate_model(Task, task_id) | ||
return make_response(jsonify({"task":task.to_dict()}), 200) | ||
|
||
@tasks_bp.route("/<task_id>", methods=["PUT"]) | ||
def update_task(task_id): | ||
task = validate_model(Task, task_id) | ||
request_body = request.get_json() | ||
|
||
task.title = request_body["title"] | ||
task.description = request_body["description"] | ||
task.completed_at = None | ||
|
||
db.session.commit() | ||
return make_response({"task":task.to_dict()}, 200) | ||
|
||
|
||
Comment on lines
+87
to
+88
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. One of the things to look at when we are in our code clean up phase is whitespace across a file. The general guideline is one newline between functions inside of a class, 2 new lines for top level functions in a file - but what's most important is to be consistent across a codebase. A little extra whitespace is often used as a visual separation between groups of related functions, or to make it clear where imports end and implementation code begins. We lose the ability to have a visual separation have meaning if we are not consistent with how they are applied. The PEP 8 style guide has a little more info on their recommendations: https://peps.python.org/pep-0008/#blank-lines |
||
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||
def complete_task(task_id): | ||
task = validate_model(Task, task_id) | ||
|
||
if task.completed_at == None: | ||
task.completed_at = datetime.now() | ||
db.session.commit() | ||
|
||
return make_response({"task": task.to_dict()}, 200) | ||
|
||
@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||
def incomplete_task(task_id): | ||
task = validate_model(Task, task_id) | ||
|
||
if task.completed_at != None: | ||
task.completed_at = None | ||
db.session.commit() | ||
|
||
return make_response({"task": task.to_dict()}, 200) | ||
|
||
|
||
@tasks_bp.route("/<task_id>", methods=["DELETE"]) | ||
def delete_task(task_id): | ||
task = validate_model(Task, task_id) | ||
|
||
db.session.delete(task) | ||
db.session.commit() | ||
|
||
return make_response({"details": 'Task 1 "Go on my daily walk 🏞" 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.
Nice choice to split up the routes into files that are more specific to the resources they work with!