-
Notifications
You must be signed in to change notification settings - Fork 97
Cedar-Rebeca Muniz #80
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?
Changes from all commits
ca85201
36c18e1
57013fd
27ca50f
d2bfcdb
635716a
df15aa3
9517cb1
2914fb9
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,24 @@ | ||
| from flask import current_app | ||
| from sqlalchemy.orm import backref | ||
| from app import db | ||
| from .task import Task | ||
|
|
||
|
|
||
| class Goal(db.Model): | ||
| goal_id = db.Column(db.Integer, primary_key=True) | ||
| title = db.Column(db.String) | ||
| tasks = db.relationship("Task", backref="goal", lazy=True, uselist=True) | ||
|
|
||
| def to_dict(self): | ||
| return { | ||
| "id": self.goal_id, | ||
| "title": self.title, | ||
| } | ||
|
|
||
| def task_list(self): | ||
| task_list = [] | ||
|
|
||
| for task in self.tasks: | ||
| task_list.append(task.to_dict()) | ||
|
|
||
| return task_list | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,25 @@ | ||
| from flask import current_app | ||
| from flask import current_app, jsonify | ||
| from app import db | ||
|
|
||
| import requests | ||
| import os | ||
| #from app.models.goal import Goal | ||
|
|
||
| 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) | ||
| is_complete = db.Column(db.Boolean) | ||
|
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.
|
||
| goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'), nullable=True) | ||
|
|
||
| def to_dict(self): | ||
| task_dict = { | ||
| "id": self.task_id, | ||
| "title": self.title, | ||
| "description": self.description, | ||
| "is_complete": self.completed_at is not None, | ||
| } | ||
| if self.goal_id: | ||
| task_dict["goal_id"] = self.goal_id | ||
|
|
||
| return task_dict | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,2 +1,231 @@ | ||||||||||
| from flask import Blueprint | ||||||||||
| from app import db | ||||||||||
| from app.models.task import Task | ||||||||||
| from app.models.goal import Goal | ||||||||||
| from flask import Blueprint, jsonify, request, abort, make_response | ||||||||||
| from datetime import datetime, timezone | ||||||||||
| import os | ||||||||||
| from dotenv import load_dotenv | ||||||||||
| import requests | ||||||||||
|
|
||||||||||
| tasks_bp = Blueprint("task_list", __name__, url_prefix="/tasks") | ||||||||||
|
|
||||||||||
| def valid_int(number, parameter_type): | ||||||||||
| try: | ||||||||||
| int(number) | ||||||||||
| except: | ||||||||||
| abort(make_response({"error": f"{parameter_type} must be an int"})), 400 | ||||||||||
|
|
||||||||||
| 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}") | ||||||||||
|
Comment on lines
+18
to
+20
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. Consider implementing a similar method for |
||||||||||
|
|
||||||||||
| @tasks_bp.route("", methods=["POST"]) | ||||||||||
| def post_new_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 jsonify({"details": "Invalid data"}), 400 | ||||||||||
|
|
||||||||||
| new_task = Task(title=request_body["title"], | ||||||||||
|
Comment on lines
+26
to
+30
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. Consider encapsulating this functionality in a helper method that either aborts for invalid data or returns the |
||||||||||
| description=request_body["description"], | ||||||||||
| completed_at=request_body["completed_at"]) | ||||||||||
|
|
||||||||||
| db.session.add(new_task) | ||||||||||
| db.session.commit() | ||||||||||
|
|
||||||||||
| response_body = { | ||||||||||
| "task": (new_task.to_dict()) | ||||||||||
| } | ||||||||||
| return jsonify(response_body), 201 | ||||||||||
|
|
||||||||||
| @tasks_bp.route("/<task_id>", methods=["GET"]) | ||||||||||
| def getting_one_task(task_id): | ||||||||||
| task = Task.query.get(task_id) | ||||||||||
|
|
||||||||||
| if task is None: | ||||||||||
| return jsonify(None), 404 | ||||||||||
|
Comment on lines
+44
to
+47
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. To DRY up your code, you could use the |
||||||||||
|
|
||||||||||
| response_body = { | ||||||||||
| "task": (task.to_dict()) | ||||||||||
| } | ||||||||||
| return jsonify(response_body), 200 | ||||||||||
|
|
||||||||||
| @tasks_bp.route("", methods=["GET"]) | ||||||||||
| def getting_tasks(): | ||||||||||
| query = Task.query # Base query | ||||||||||
|
|
||||||||||
| # Query params, adding to query where indicated | ||||||||||
| sort = request.args.get("sort") | ||||||||||
| if sort == "asc": | ||||||||||
| query = query.order_by(Task.title) | ||||||||||
| elif sort == "desc": | ||||||||||
| query = query.order_by(Task.title.desc()) | ||||||||||
|
|
||||||||||
| query = query.all() # Final query | ||||||||||
|
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. Consider naming query
Suggested change
Suggested change
|
||||||||||
|
|
||||||||||
| # Returns jsonified list of task dicionaries | ||||||||||
| return jsonify([task.to_dict() for task in query]) | ||||||||||
|
|
||||||||||
| @tasks_bp.route("/<task_id>", methods=["PUT"]) | ||||||||||
| def put_task(task_id): | ||||||||||
| task = Task.query.get(task_id) | ||||||||||
| if task is None: | ||||||||||
| return jsonify(None), 404 | ||||||||||
|
|
||||||||||
| request_body = request.get_json() | ||||||||||
| task.title = request_body["title"] | ||||||||||
| task.description = request_body["description"] | ||||||||||
|
|
||||||||||
| db.session.commit() | ||||||||||
|
|
||||||||||
| response_body = { | ||||||||||
| "task": (task.to_dict()) | ||||||||||
| } | ||||||||||
| return jsonify(response_body), 200 | ||||||||||
|
|
||||||||||
| @tasks_bp.route("/<task_id>", methods=["DELETE"]) | ||||||||||
| def delete_task(task_id): | ||||||||||
| task = Task.query.get(task_id) | ||||||||||
| if task is None: | ||||||||||
| return jsonify(None), 404 | ||||||||||
|
Comment on lines
+89
to
+91
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 is another place you can use the helper function |
||||||||||
|
|
||||||||||
| db.session.delete(task) | ||||||||||
| db.session.commit() | ||||||||||
| return jsonify({ | ||||||||||
| 'details': f'Task {task.task_id} "{task.title}" successfully deleted' | ||||||||||
| }), 200 | ||||||||||
|
|
||||||||||
| # Wave 3/new endpoints updates task as complete | ||||||||||
| @tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||||||||||
| def update_task_completion(task_id): | ||||||||||
| task= get_task_from_id(task_id) | ||||||||||
| task.is_complete=True | ||||||||||
| task.completed_at = datetime.now() | ||||||||||
| db.session.commit() | ||||||||||
|
|
||||||||||
| slack_key = os.environ.get("SLACK_KEY") | ||||||||||
| slack_url = os.environ.get("SLACK_URL") | ||||||||||
| channel_id = os.environ.get("CHANNEL_ID") | ||||||||||
|
Comment on lines
+108
to
+109
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.
|
||||||||||
| requests.post(slack_url, headers= {'Authorization': f"Bearer {slack_key}"}, \ | ||||||||||
| data= {'channel' : f"{channel_id}", 'text' : f"Someone just completed the task My Beautiful Task"}) | ||||||||||
|
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. Great work implementing this functionality. Consider encapsulating in a helper function to enhance readability and flexibility (You may want to use this function when someone updates a task, for instance). |
||||||||||
| return jsonify({"task": task.to_dict()}), 200 | ||||||||||
|
|
||||||||||
| # route that will mark item as incomplete | ||||||||||
| @tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||||||||||
| def update_task_incomplete(task_id): | ||||||||||
| task= get_task_from_id(task_id) | ||||||||||
|
|
||||||||||
| task.is_complete=False | ||||||||||
| task.completed_at=None | ||||||||||
| # db.session.add(task) | ||||||||||
| db.session.commit() | ||||||||||
| return jsonify({"task": task.to_dict()}), 200 | ||||||||||
|
|
||||||||||
| ##########GOAL_ROUTES############ | ||||||||||
| ################################# | ||||||||||
|
|
||||||||||
| goals_bp = Blueprint("goals", __name__, url_prefix="/goals") | ||||||||||
|
|
||||||||||
| @goals_bp.route("", methods=["POST"]) #my decorator | ||||||||||
| def create_goal(): | ||||||||||
| request_body = request.get_json() | ||||||||||
|
|
||||||||||
| if "title" not in request_body: | ||||||||||
| return {"details": "Invalid data"}, 400 | ||||||||||
|
|
||||||||||
| new_goal = Goal( | ||||||||||
| title=request_body["title"] | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| db.session.add(new_goal) | ||||||||||
| db.session.commit() | ||||||||||
|
|
||||||||||
| return {"goal": new_goal.to_dict()}, 201 | ||||||||||
|
|
||||||||||
| @goals_bp.route("", methods=["GET"]) | ||||||||||
| def get_one_goal(): | ||||||||||
| goals = Goal.query.all() | ||||||||||
|
|
||||||||||
| goal_list = [] | ||||||||||
|
|
||||||||||
| if not goals: | ||||||||||
| return jsonify(goal_list), 200 | ||||||||||
|
|
||||||||||
| for goal in goals: | ||||||||||
| goal_list.append(goal.to_dict()) | ||||||||||
|
|
||||||||||
| return jsonify(goal_list), 200 | ||||||||||
|
|
||||||||||
| @goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||||||||||
| def read_tasks_for_goal(goal_id): | ||||||||||
| goal = Goal.query.get(goal_id) | ||||||||||
|
|
||||||||||
| if not goal: | ||||||||||
| return "", 404 | ||||||||||
|
|
||||||||||
| return { | ||||||||||
| "id": goal.goal_id, | ||||||||||
| "title": goal.title, | ||||||||||
| "tasks": goal.task_list() | ||||||||||
|
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 use of an instance method! |
||||||||||
| }, 200 | ||||||||||
|
|
||||||||||
| @goals_bp.route("/<goal_id>", methods=["GET"]) | ||||||||||
| def get_goal(goal_id): | ||||||||||
| goal = Goal.query.get(goal_id) | ||||||||||
|
|
||||||||||
| if goal is None: | ||||||||||
| return jsonify(None), 404 | ||||||||||
|
|
||||||||||
| response_body = { | ||||||||||
| "goal": (goal.to_dict()) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return jsonify(response_body), 200 | ||||||||||
|
|
||||||||||
| @goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||||||||||
| def create_tasks_for_goal(goal_id): | ||||||||||
| request_body = request.get_json() | ||||||||||
|
|
||||||||||
| goal = Goal.query.get(goal_id) | ||||||||||
|
|
||||||||||
| if not goal: | ||||||||||
| return "", 404 | ||||||||||
|
|
||||||||||
| for id in request_body['task_ids']: | ||||||||||
| goal.tasks.append(Task.query.get(id)) | ||||||||||
|
|
||||||||||
| db.session.commit() | ||||||||||
|
|
||||||||||
| return { | ||||||||||
| "id": goal.goal_id, | ||||||||||
| "task_ids": request_body['task_ids'] | ||||||||||
| }, 200 | ||||||||||
|
|
||||||||||
| @goals_bp.route("/<goal_id>", methods=["PUT", "PATCH"]) | ||||||||||
| def update_goal(goal_id): | ||||||||||
| goal = Goal.query.get(goal_id) | ||||||||||
|
|
||||||||||
| if not goal: | ||||||||||
| return "", 404 | ||||||||||
|
|
||||||||||
| request_body = request.get_json() | ||||||||||
|
|
||||||||||
| goal.title = request_body["title"] | ||||||||||
|
|
||||||||||
| db.session.commit() | ||||||||||
|
|
||||||||||
| return {"goal": goal.to_dict()}, 200 | ||||||||||
|
|
||||||||||
| @goals_bp.route("/<goal_id>", methods=["DELETE"]) | ||||||||||
| def delete_goal(goal_id): | ||||||||||
| goal = Goal.query.get(goal_id) | ||||||||||
|
|
||||||||||
| if not goal: | ||||||||||
| return "", 404 | ||||||||||
|
|
||||||||||
| db.session.delete(goal) | ||||||||||
| db.session.commit() | ||||||||||
|
|
||||||||||
| return {"details": | ||||||||||
| f'Goal {goal.goal_id} "{goal.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.
Nice work encapsulating this functionality in an instance method.