-
Notifications
You must be signed in to change notification settings - Fork 146
Tigers - Neida #130
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?
Tigers - Neida #130
Changes from all commits
afa7ed9
0e71192
b07180d
a973e17
b31d5a7
bdb8dcb
bad0ca2
f43af84
3017091
2d97165
341a47c
a998ec8
dcb5b63
629df24
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,5 +1,20 @@ | ||
| from app import db | ||
| from flask import abort, make_response | ||
|
|
||
|
|
||
| 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 update(self,request_body): | ||
| try: | ||
| self.title = request_body["title"] | ||
| except KeyError as error: | ||
| abort(make_response({'message': f"Missing attribute: {error}"})) | ||
|
|
||
| def to_json(self): | ||
| return { | ||
| "id": self.goal_id, | ||
| "title": self.title, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,50 @@ | ||
| from app import db | ||
| from flask import make_response, abort | ||
|
|
||
|
|
||
| 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) | ||
| description = db.Column(db.String) | ||
| completed_at = db.Column(db.DateTime, nullable=True, default=None) | ||
| goal = db.relationship("Goal", back_populates="tasks") | ||
| goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable=True) | ||
|
|
||
| def to_json(self): | ||
| if self.completed_at: | ||
| is_complete = True | ||
| else: | ||
| is_complete = False | ||
|
|
||
| if self.goal_id: | ||
| return { | ||
| "id": self.task_id, | ||
| "title": self.title, | ||
| "description": self.description, | ||
| "is_complete": is_complete, | ||
| "goal_id": self.goal_id | ||
| } | ||
| else: | ||
| return { | ||
| "id": self.task_id, | ||
| "title": self.title, | ||
| "description": self.description, | ||
| "is_complete": is_complete | ||
| } | ||
|
|
||
| def update(self,request_body): | ||
| try: | ||
| self.title = request_body["title"] | ||
| self.description = request_body["description"] | ||
| except KeyError as error: | ||
| abort(make_response({'message': f"Missing attribute: {error}"})) | ||
|
|
||
| @classmethod | ||
| def create(cls, request_body): | ||
| new_task = cls( | ||
| title = request_body["title"], | ||
| description = request_body["description"], | ||
| ) | ||
| return new_task | ||
|
|
||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| from app import db | ||
|
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 job splitting this into goal routes and task routes! |
||
| from app.models.goal import Goal | ||
| from app.models.task import Task | ||
| from flask import Blueprint, request, jsonify, make_response | ||
| from app.routes.task_routes import validate_id | ||
|
|
||
| goal_bp = Blueprint("goals", __name__, url_prefix="/goals") | ||
|
|
||
| # ================================ | ||
| # Create one goal | ||
| # ================================ | ||
|
Comment on lines
+9
to
+11
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. I like how the borders here make these comments pop. |
||
|
|
||
| @goal_bp.route("", methods=["POST"]) | ||
| def create_goal(): | ||
| request_body = request.get_json() | ||
|
|
||
| try: | ||
| new_goal = Goal(title=request_body["title"]) | ||
| except KeyError: | ||
| return make_response({"details": "Invalid data"}, 400) | ||
|
|
||
| db.session.add(new_goal) | ||
| db.session.commit() | ||
|
|
||
| return jsonify({"goal": new_goal.to_json()}), 201 | ||
|
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 you are returning a dictionary, you shouldn't need to use I wager there's other places where you used |
||
|
|
||
| # ================================== | ||
| # Get all goals | ||
| # ================================== | ||
|
|
||
| @goal_bp.route("", methods=["GET"]) | ||
| def get_all_goals(): | ||
| all_goals = Goal.query.all() | ||
| results_list = [] | ||
| for goal in all_goals: | ||
| results_list.append(goal.to_json()) | ||
| return jsonify(results_list), 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. Here, though, you do have to use That being said, if your HTTP status code is 200, you don't need to include it in your return value, so you could just do That being said, keeping it can be easily justified as a way to be more explicit, which is often good style IMO. |
||
|
|
||
| # ================================== | ||
| # Get one goal by id number | ||
| # ================================== | ||
|
|
||
| @goal_bp.route("/<goal_id>", methods=["GET"]) | ||
| def get_one_goal(goal_id): | ||
| return jsonify({"goal": validate_id(Goal, goal_id).to_json()}), 200 | ||
|
Comment on lines
+43
to
+45
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 works fine and is succinct, but there is a lot going on that may make this a bit hard to parse for a reader. That is, there's a call to Beyond just being hard to parse, this also has disadvantages when debugging. For example, if you wanted to debug just this call to I'm probably being nitpicky, but I guess I wanted to point out some of the issues with succinctness! |
||
|
|
||
| # ================================== | ||
| # Update one goal | ||
| # ================================== | ||
|
|
||
| @goal_bp.route("/<goal_id>", methods=["PUT"]) | ||
| def update_one_goal(goal_id): | ||
| request_body = request.get_json() | ||
| goal = validate_id(Goal, goal_id) | ||
| goal.update(request_body) | ||
|
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. I like the use of a method off of the |
||
| db.session.commit() | ||
| return jsonify({"goal": goal.to_json()}), 200 | ||
|
|
||
| # ================================== | ||
| # Delete one goal by id | ||
| # ================================== | ||
|
|
||
| @goal_bp.route("/<goal_id>", methods=["DELETE"]) | ||
| def delete_one_goal(goal_id): | ||
| goal = validate_id(Goal, 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 |
||
|
|
||
| # ====================================== | ||
| # Get all tasks that are under one goal | ||
| # ====================================== | ||
| @goal_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
| def get_all_tasks_for_one_goal(goal_id): | ||
| goal = validate_id(Goal, goal_id) | ||
| all_goal_tasks = [] | ||
| for task in goal.tasks: | ||
| all_goal_tasks.append(Task.to_json(task)) | ||
| return make_response({"id": goal.goal_id, "title": goal.title, "tasks": all_goal_tasks}, 200) | ||
|
|
||
| # ====================================== | ||
| # Post tasks to one goal | ||
| # ====================================== | ||
| @goal_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
| def associate_tasks_with_one_goal(goal_id): | ||
| request_body = request.get_json() | ||
| goal = validate_id(Goal,goal_id) | ||
| task_list = request_body["task_ids"] | ||
| for task_number in task_list: | ||
| task = validate_id(Task, task_number) | ||
| if task not in goal.tasks: | ||
| goal.tasks.append(task) | ||
| db.session.commit() | ||
| return make_response({"id": goal.goal_id, "task_ids": task_list}, 200) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| from flask import Blueprint, jsonify, abort, make_response, request | ||
| from app import db | ||
| from app.models.task import Task | ||
| from datetime import datetime | ||
| import os | ||
| import requests | ||
|
|
||
| task_bp = Blueprint("tasks", __name__, url_prefix="/tasks") | ||
|
|
||
| # ================================ | ||
| # Create one task | ||
| # ================================ | ||
| @task_bp.route("", methods=["POST"]) | ||
| def create_task(): | ||
| request_body = request.get_json() | ||
|
|
||
| try: | ||
| new_task = Task.create(request_body) | ||
| except KeyError: | ||
| return make_response({"details": "Invalid data"}, 400) | ||
|
|
||
| db.session.add(new_task) | ||
| db.session.commit() | ||
|
|
||
| return jsonify({"task": new_task.to_json()}), 201 | ||
|
|
||
| # ================================== | ||
| # Get all tasks | ||
| # ================================== | ||
| @task_bp.route("", methods=["GET"]) | ||
| def get_all_tasks(): | ||
| sort_query = request.args.get("sort") | ||
| if sort_query == 'asc': | ||
| all_tasks = Task.query.order_by(Task.title.asc()) | ||
| elif sort_query == 'desc': | ||
| all_tasks = Task.query.order_by(Task.title.desc()) | ||
| else: | ||
| all_tasks = Task.query.all() | ||
|
|
||
| results_list = [] | ||
| for task in all_tasks: | ||
| results_list.append(task.to_json()) | ||
|
|
||
| return jsonify(results_list), 200 | ||
|
|
||
| # ================================== | ||
| # Get one task by id number | ||
| # ================================== | ||
| @task_bp.route("/<task_id>", methods=["GET"]) | ||
| def get_one_task(task_id): | ||
| return jsonify({"task": validate_id(Task, task_id).to_json()}), 200 | ||
|
|
||
| # ================================== | ||
| # Update one task | ||
| # ================================== | ||
| @task_bp.route("/<task_id>", methods=["PUT"]) | ||
| def update_one_task(task_id): | ||
| request_body = request.get_json() | ||
| task = validate_id(Task, task_id) | ||
| task.update(request_body) | ||
|
|
||
| db.session.commit() | ||
|
|
||
| return jsonify({"task": task.to_json()}), 200 | ||
|
|
||
| # ================================== | ||
| # Delete one task by id | ||
| # ================================== | ||
| @task_bp.route("/<task_id>", methods=["DELETE"]) | ||
| def delete_one_task(task_id): | ||
| task = validate_id(Task, task_id) | ||
|
|
||
| db.session.delete(task) | ||
| db.session.commit() | ||
|
|
||
| return make_response({"details":f'Task {task.task_id} "{task.title}" successfully deleted'}, 200) | ||
|
|
||
| # ================================== | ||
| # update one task's completeness | ||
| # ================================== | ||
| @task_bp.route("/<task_id>/<mark>", methods=["PATCH"]) | ||
| def mark_tasks_complete_or_incomplete(task_id, mark): | ||
| task = validate_id(Task, task_id) | ||
| task.completed_at = datetime.now() if mark == "mark_complete" else 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. Nice, I like how succinct this is, and saves having to define two different endpoints. There's a downside in that I can provide other But this is still a good idea, and there's a pretty easy fix. |
||
| send_to_slack(task.title, "task-notifications", mark) | ||
| db.session.commit() | ||
| return jsonify({"task": task.to_json()}), 200 | ||
|
|
||
| # ================================== | ||
| # Helper function to validate id | ||
| # ================================== | ||
| def validate_id(class_name,id): | ||
| try: | ||
| id = int(id) | ||
| except: | ||
| abort(make_response({"message":f"Id {id} is an invalid id"}, 400)) | ||
|
|
||
| query_result = class_name.query.get(id) | ||
| if not query_result: | ||
| abort(make_response({"message":f"Id {id} not found"}, 404)) | ||
|
|
||
| return query_result | ||
|
Comment on lines
+92
to
+102
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. Two minor things:
|
||
|
|
||
| # ================================== | ||
| # Helper function to send message to slack | ||
| # ================================== | ||
| def send_to_slack(title, channel_name, mark): | ||
| header_data = {'Authorization': f"Bearer {os.environ.get('SLACK_BOT_TOKEN')}"} | ||
| message_data = {'channel': channel_name, 'text': f"Someone just completed the task {title}"} | ||
| if mark == 'mark_complete': | ||
| requests.post('https://slack.com/api/chat.postMessage', data=message_data, headers=header_data) | ||
|
Comment on lines
+107
to
+111
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. While I made a comment about the If you began expanding the Slack integration to |
||
|
|
||
|
|
||
|
|
||
|
|
||
| # original implementation using slack_sdk to make calls to slack API | ||
|
|
||
|
Comment on lines
+116
to
+117
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 job at explaining why this commented code was here! In general, I recommend getting rid of commented code, especially if it's been committed already and thus is available from the source control history, but if that doesn't work for some reason, this is exactly what you should do! |
||
| # import logging | ||
| # from slack_sdk import WebClient | ||
| # from slack_sdk.errors import SlackApiError | ||
|
|
||
| # def send_to_slack(title, channel_name, mark): | ||
| # if mark == "mark_complete": | ||
| # client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN")) | ||
| # logger = logging.getLogger(__name__) | ||
| # try: | ||
| # # Call the chat.postMessage method using the WebClient | ||
| # result = client.chat_postMessage( | ||
| # channel=channel_name, | ||
| # text= f"Someone just completed the task '{title}'" | ||
| # ) | ||
| # logger.info(result) | ||
|
|
||
| # except SlackApiError as e: | ||
| # logger.error(f"Error posting message: {e}") | ||
| 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 |
Uh oh!
There was an error while loading. Please reload this page.
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.
This works fine, but you can cut down on a bit of repetition using
booland a local variable for your return value: