-
Notifications
You must be signed in to change notification settings - Fork 44
task_list_api -Sunitha -Sphinx #34
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: main
Are you sure you want to change the base?
Changes from all commits
a4bda72
2c511d4
8888e1b
e410b19
681df2b
de119b7
77f3cba
59dc8bd
8eb38ad
69fe2bf
77f22b6
aadbb26
1bb52da
e7a22aa
9e389bc
483bfec
e41e51a
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 |
|---|---|---|
| @@ -1,5 +1,15 @@ | ||
| from sqlalchemy.orm import Mapped, mapped_column | ||
| from sqlalchemy.orm import Mapped, mapped_column, relationship | ||
| from ..db import db | ||
|
|
||
|
|
||
| class Goal(db.Model): | ||
| id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) | ||
| title: Mapped[str] | ||
| tasks: Mapped[list["Task"]] = relationship("Task", back_populates="goal") | ||
|
|
||
| def to_dict(self): | ||
| return dict(id=self.id, title=self.title) | ||
|
|
||
| @classmethod | ||
| def from_dict(cls, goal_data): | ||
| return cls(title=goal_data["title"]) | ||
|
Comment on lines
+10
to
+15
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. 👍 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,34 @@ | ||
| from sqlalchemy.orm import Mapped, mapped_column | ||
| from sqlalchemy.orm import Mapped, mapped_column, relationship | ||
| from sqlalchemy import ForeignKey | ||
|
Comment on lines
+1
to
+2
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.
Same with The most current documentation and our Learn content use syntax that does not include |
||
| from ..db import db | ||
| from datetime import datetime | ||
| from typing import Optional | ||
|
|
||
|
|
||
| class Task(db.Model): | ||
| id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) | ||
| title: Mapped[str] = mapped_column(String, nullable=False) | ||
| description: Mapped[str] | ||
| completed_at: Mapped[Optional[datetime]] | ||
| goal_id: Mapped[Optional[int]] = mapped_column(ForeignKey("goal.id")) | ||
| goal: Mapped[Optional["Goal"]] = relationship("Goal", back_populates="tasks") | ||
|
|
||
| def to_dict(self): | ||
| task_dict = { | ||
| "id": self.id, | ||
| "title": self.title, | ||
| "description": self.description, | ||
| "is_complete": task.is_complete()}) | ||
| } | ||
| if self.goal_id is not None: | ||
| task_dict["goal_id"] = self.goal_id | ||
| return task_dict | ||
|
|
||
| @classmethod | ||
| def from_dict(cls, task_data): | ||
| return cls( | ||
| goal_id=task_data.get("goal_id"), | ||
| title=task_data["title"], | ||
| description=task_data["description"], | ||
| completed_at=task_data.get("completed_at", None), | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,142 @@ | ||
| from flask import Blueprint | ||
| from flask import Blueprint, abort, make_response, request, Response | ||
| from app.models.goal import Goal | ||
| from app.models.task import Task | ||
| from ..db import db | ||
| from datetime import datetime | ||
|
|
||
| goals_bp = Blueprint("goals_bp", __name__, url_prefix="/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. In Learn and in the livecode for Flasky, we name blueprints for each model If we have multiple route files that each have a blueprint named from .routes.task_routes import bp as tasks_bp
from .routes.goal_routes import bp as goals_bp |
||
|
|
||
|
|
||
| @goals_bp.post("") | ||
| def create_goal(): | ||
| request_body = request.get_json() | ||
|
|
||
| try: | ||
| new_goal = Goal.from_dict(request_body) | ||
|
|
||
| except KeyError as error: | ||
| response = {"details": f"Invalid data - missing {error.args[0]}"} | ||
| abort(make_response(response, 400)) | ||
|
|
||
| db.session.add(new_goal) | ||
| db.session.commit() | ||
|
|
||
| response = {"goal": new_goal.to_dict()} | ||
| return response, 201 | ||
|
|
||
|
|
||
| @goals_bp.get("") | ||
| def get_all_goals(): | ||
| query = db.select(Goal) | ||
|
|
||
| title_param = request.args.get("title") | ||
| sort = request.args.get("sort", "asc") # Default to ascending order | ||
|
|
||
| query = db.select(Goal) | ||
|
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. Did you accidentally write line 35 again? It repeats what's on line 30 and since you already have a Selet object, you don't need to write this again. |
||
|
|
||
| if title_param: | ||
| query = query.where(Goal.title.ilike(f"%{title_param}%")) | ||
|
|
||
| if sort == "desc": | ||
| query = query.order_by(Goal.title.asc()) | ||
|
|
||
| goals = db.session.scalars(query) | ||
| goals_response = [goal.to_dict() for goal in goals] | ||
|
|
||
| return goals_response, 200 | ||
|
|
||
|
|
||
| @goals_bp.get("/<goal_id>") | ||
| def get_single_goal(goal_id): | ||
| goal = validate_goal(goal_id) | ||
|
|
||
| return {"goal": goal.to_dict()} | ||
|
|
||
|
|
||
| @goals_bp.put("/<goal_id>") | ||
| def update_goal(goal_id): | ||
| goal = validate_goal(goal_id) | ||
| request_body = request.get_json() | ||
|
|
||
| goal.title = request_body["title"] | ||
|
|
||
| db.session.commit() | ||
| response = {"goal": goal.to_dict()} | ||
| return response, 200 | ||
|
|
||
|
|
||
| @goals_bp.patch("/<goal_id>/mark_complete") | ||
| def mark_goal_complete(goal_id): | ||
| goal = validate_goal(goal_id) | ||
| goal.completed_at = datetime.now() | ||
| db.session.commit() | ||
|
|
||
| return {"goal": goal.to_dict()}, 200 | ||
|
|
||
|
|
||
| @goals_bp.patch("/<goal_id>/mark_incomplete") | ||
| def mark_goal_incomplete(goal_id): | ||
| goal = validate_goal(goal_id) | ||
| goal.completed_at = None | ||
|
|
||
| db.session.commit() | ||
|
|
||
| return {"goal": goal.to_dict()}, 200 | ||
|
|
||
|
|
||
| @goals_bp.delete("/<goal_id>") | ||
| def delete_goal(goal_id): | ||
| goal = validate_goal(goal_id) | ||
|
|
||
| db.session.delete(goal) | ||
| db.session.commit() | ||
| response = {"details": f'Goal {goal_id} "{goal.title}" successfully deleted'} | ||
| return response, 200 | ||
|
|
||
|
|
||
| @goals_bp.post("/<goal_id>/tasks") | ||
| def assign_tasks_to_goal(goal_id): | ||
| goal = validate_goal(goal_id) | ||
| request_body = request.get_json() | ||
| task_ids = request_body.get("task_ids", []) | ||
|
|
||
| tasks = [validate_model(Task, task_id) for task_id in task_ids] | ||
|
|
||
| goal.tasks.extend(tasks) | ||
| db.session.commit() | ||
|
|
||
| return {"id": goal.id, "task_ids": task_ids}, 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. Notice that You'd have to grab tasks from goal like: tasks_from_goal = goal.tasks
task_ids_from_goal = [task.id for task in tasks_from_goal]
return {
"id": goal.id,
"task_ids": task_ids_from_goal
} |
||
|
|
||
|
|
||
| @goals_bp.get("/<goal_id>/tasks") | ||
| def get_tasks_by_goal(goal_id): | ||
| goal = validate_goal(goal_id) | ||
| goal_dict = goal.to_dict() | ||
|
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 we are creating a dictionary that represents a goal, it might be more descriptive to name this variable |
||
| goal_dict["tasks"] = [task.to_dict() for task in goal.tasks] | ||
| return goal_dict | ||
|
|
||
|
|
||
| @goals_bp.get("<goal_id>/tasks/<task_id>") | ||
| def get_one_task_by_goal(goal_id, task_id): | ||
|
|
||
| goal = validate_goal(Goal, goal_id) | ||
| task = validate_goal(Task, task_id) | ||
|
|
||
| if task in goal.tasks: | ||
| goal_dict = goal.to_dict() | ||
| goal_dict["task"] = task.to_dict() | ||
| return goal_dict | ||
| return {"details": f"Task {task.id} not found for Goal {goal.id}"}, 404 | ||
|
|
||
|
|
||
| def validate_goal(goal_id): | ||
| try: | ||
| goal_id = int(goal_id) | ||
| except ValueError: | ||
| abort(400, description="Invalid goal_id") | ||
|
|
||
| goal = Goal.query.get(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. This is deprecated syntax. The syntax we use, which follows the most up to date SQLAlchemy syntax, is: query = db.select(cls).where(cls.id == model_id)
model = db.session.scalar(query) |
||
| if goal is None: | ||
| abort(make_response({"message": f"goal {goal_id} not found"}, 404)) | ||
|
|
||
| return goal | ||
|
Comment on lines
+132
to
+142
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 method is nearly identical to You can create a file |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,119 @@ | ||
| from flask import Blueprint | ||
| from flask import Blueprint, abort, make_response, request, Response | ||
| from app.models.task import Task | ||
| from ..db import db | ||
| from datetime import datetime | ||
| from app.models.task import Task | ||
|
|
||
| tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") | ||
|
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. Same comment as above in |
||
|
|
||
|
|
||
|
|
||
| @tasks_bp.post("") | ||
| def create_task(): | ||
| request_body = request.get_json() | ||
|
|
||
| try: | ||
| new_task = Task.from_dict(request_body) | ||
|
|
||
| except KeyError: | ||
| response = {"details": "Invalid data"} | ||
| abort(make_response(response, 400)) | ||
|
|
||
|
|
||
| db.session.add(new_task) | ||
| db.session.commit() | ||
|
Comment on lines
+13
to
+24
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. Same comment as above in |
||
|
|
||
| response = {"task": new_task.to_dict()} | ||
| return response, 201 | ||
|
|
||
|
|
||
| @tasks_bp.get("") | ||
| def get_all_tasks(): | ||
| query = db.select(Task) | ||
| description_param = request.args.get("description") | ||
| if description_param: | ||
| query = query.where(Task.description.ilike(f"%{description_param}%")).order_by(Task.id) | ||
|
|
||
| title_param = request.args.get("title") | ||
| sort = request.args.get("sort", "asc") # Default to ascending order | ||
|
|
||
| query = db.select(Task) | ||
|
|
||
| if title_param: | ||
| query = query.where(Task.title.ilike(f"%{title_param}%")) | ||
|
|
||
| if sort == "desc": | ||
| query = query.order_by(Task.title.desc()) | ||
| else: | ||
| query = query.order_by(Task.title.asc()) | ||
|
|
||
| tasks = db.session.scalars(query) | ||
| tasks_response = [task.to_dict() for task in tasks] | ||
|
|
||
| return tasks_response, 200 | ||
|
|
||
|
|
||
| @tasks_bp.get("/<task_id>") | ||
| def get_single_task(task_id): | ||
| task = validate_task(task_id) | ||
|
|
||
|
|
||
| return {"task": task.to_dict()} | ||
|
Comment on lines
+58
to
+61
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. 👍 |
||
|
|
||
|
|
||
| @tasks_bp.put("/<task_id>") | ||
| def update_task(task_id): | ||
| task = validate_task(task_id) | ||
| request_body = request.get_json() | ||
|
|
||
| task.title = request_body["title"] | ||
| task.description = request_body.get("description") | ||
| task.completed_at = request_body.get("completed_at") | ||
|
|
||
| db.session.commit() | ||
| response = {"task": task.to_dict()} | ||
| return response, 200 | ||
|
|
||
| @tasks_bp.patch("/<task_id>/mark_complete") | ||
| def mark_task_complete(task_id): | ||
| task = validate_task(task_id) | ||
| task.completed_at = datetime.now() | ||
| db.session.commit() | ||
|
|
||
| return {"task": task.to_dict()}, 200 | ||
|
Comment on lines
+78
to
+83
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. For the sake of time, I went to your repo and found your other branch to look at your Slack implementation. Note: I cannot leave comments on a codebase. That's why we have you open PRs for your projects. In the future, please make sure all of your solution is in your PR. I'm copy and pasting your helper function here and leaving my review comments as comments in your code def send_slack_message(task_title):
# constants should be named using all capital letters
token = os.environ.get("SLACK_BOT_TOKEN")
if not token:
raise ValueError("Slack Bot Token is not set in environment variables.")
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
payload = {"channel": SLACK_CHANNEL, "text": f"Beautiful task {task_title}"}
response = requests.post(SLACK_API_URL, headers=headers, json=payload)
if response.status_code != 200:
print("Failed to send Slack message:", response.json())
else:
print("Slack message sent successfully.") |
||
|
|
||
| @tasks_bp.patch("/<task_id>/mark_incomplete") | ||
| def mark_task_incomplete(task_id): | ||
| task = validate_task(task_id) | ||
| task.completed_at = None | ||
|
|
||
| db.session.commit() | ||
|
|
||
| return {"task": task.to_dict()}, 200 | ||
|
|
||
|
|
||
| @tasks_bp.delete("/<task_id>") | ||
| def delete_task(task_id): | ||
| task = validate_task(task_id) | ||
|
|
||
| db.session.delete(task) | ||
| db.session.commit() | ||
| response = { | ||
| "details": f'Task {task_id} "{task.title}" successfully deleted' | ||
| } | ||
| return response, 200 | ||
|
|
||
| def validate_task(task_id): | ||
| try: | ||
| task_id = int(task_id) | ||
| except ValueError: | ||
| response = {"details": f"Task {task_id} invalid"} | ||
| abort(make_response(response, 404)) | ||
|
|
||
| task = db.session.get(Task,task_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. This correctly uses the new syntax (instead of the deprecated syntax However, In our curriculum, we prefer |
||
|
|
||
| if not task: | ||
| response = {"message": f"task {task_id} not found"} | ||
| abort(make_response(response, 404)) | ||
|
|
||
| return task | ||
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.
Consider whether the this column for Goal should be nullable. It feels odd to allow someone to create a goal that gets saved to the DB without a title.