-
Notifications
You must be signed in to change notification settings - Fork 44
C22_Marjana_Khatun #22
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
06fa038
89525c1
36701e5
86daa5a
18f58e1
70add0a
3ba5371
798b808
5a57ab1
44f0691
3b19323
a54058d
795ea14
bd9a787
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,17 @@ | ||
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(back_populates="goal") | ||
|
||
def to_dict(self): | ||
return dict ( | ||
id=self.id, | ||
title=self.title, | ||
) | ||
@classmethod | ||
Comment on lines
+13
to
+14
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. We should leave a blank line between these functions to create visual space for readers - it makes it easier to quickly scan through a file when the functions are very clear from each other. |
||
def from_dict(cls, goal_data): | ||
return cls( | ||
title=goal_data["title"]) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,5 +1,43 @@ | ||||||
from sqlalchemy.orm import Mapped, mapped_column | ||||||
from sqlalchemy.orm import Mapped, mapped_column , relationship | ||||||
from sqlalchemy import ForeignKey | ||||||
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] | ||||||
description : Mapped [str] | ||||||
completed_at: Mapped[Optional[datetime]] | ||||||
goal_id: Mapped[Optional[int]] = mapped_column(ForeignKey("goal.id")) | ||||||
goal : Mapped[Optional["Goal"]] = relationship(back_populates="tasks") | ||||||
Comment on lines
+1
to
+13
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 nice set up work for both your model classes! 🙌🏻 |
||||||
|
||||||
def to_dict(self): | ||||||
is_complete_task= False | ||||||
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.
Suggested change
|
||||||
if self.completed_at: | ||||||
is_complete_task = True | ||||||
|
||||||
task_dict = dict( | ||||||
id=self.id, | ||||||
title=self.title, | ||||||
description=self.description, | ||||||
is_complete=is_complete_task, | ||||||
|
||||||
) | ||||||
if self.goal: | ||||||
task_dict["goal_id"] = self.goal.id | ||||||
Comment on lines
+20
to
+28
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. The spacing is a little weird here, I recommend removing the blank line inside the dict declaration and adding a blank line after to separate the creation from the goal check: task_dict = dict(
id=self.id,
title=self.title,
description=self.description,
is_complete=is_complete_task,
)
if self.goal:
task_dict["goal_id"] = self.goal.id |
||||||
|
||||||
return task_dict | ||||||
|
||||||
@classmethod | ||||||
def from_dict(cls, task_data): | ||||||
return cls( | ||||||
title=task_data["title"], | ||||||
description=task_data["description"], | ||||||
completed_at=None, | ||||||
goal_id=task_data.get("goal_id", None) | ||||||
|
||||||
) | ||||||
|
||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,106 @@ | ||
from flask import Blueprint | ||
from flask import Blueprint,request,abort,make_response | ||
from app.models.goal import Goal | ||
from ..db import db | ||
from .route_utilities import validate_model | ||
from app.models.task import Task | ||
|
||
|
||
bp = Blueprint("goals_bp", __name__, url_prefix="/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"} | ||
abort(make_response(response,400)) | ||
|
||
db.session.add(new_goal) | ||
db.session.commit() | ||
|
||
response = {"goal" : new_goal.to_dict()} | ||
return response,201 | ||
|
||
@bp.get("") | ||
def get_all_goals(): | ||
|
||
query = db.select(Goal) | ||
sorted_title = request.args.get("sort") | ||
if sorted_title == "desc": | ||
query = query.order_by(Goal.title.desc()) | ||
else: | ||
query = query.order_by(Goal.title.asc()) | ||
|
||
goals = db.session.scalars(query) | ||
|
||
goals_response =([goal.to_dict() for goal in goals],200) | ||
|
||
return goals_response | ||
|
||
@bp.get("/<goal_id>") | ||
def get_single_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
|
||
return {"goal" : goal.to_dict()} | ||
|
||
@bp.put("/<goal_id>") | ||
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 {"goal" : goal.to_dict()} | ||
|
||
@bp.delete("/<goal_id>") | ||
def delete_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
|
||
db.session.delete(goal) | ||
db.session.commit() | ||
|
||
return { | ||
"details": 'Goal 1 "Build a habit of going outside daily" successfully deleted' | ||
} | ||
|
||
@bp.post("/<goal_id>/tasks") | ||
def create_task_with_goal_id(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 function will look up existing tasks and associate them with an existing goal. Since we don't create any new tasks, how could we rename this route function to better reflect the actions it is taking? |
||
goal = validate_model(Goal, goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
task_ids = request_body["task_ids"] | ||
for task_id in task_ids: | ||
new_task_id = validate_model(Task, task_id) | ||
|
||
goal.tasks.append(new_task_id) | ||
|
||
db.session.commit() | ||
|
||
response = { | ||
"id": goal.id, | ||
"task_ids" : task_ids | ||
} | ||
|
||
return response | ||
|
||
@bp.get("/<goal_id>/tasks") | ||
def get_tasks_of_goal(goal_id): | ||
goal = validate_model(Goal,goal_id) | ||
|
||
all_tasks= [] | ||
for task in goal.tasks: | ||
all_tasks.append(task.to_dict()) | ||
Comment on lines
+96
to
+98
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 would be a great place for a list comprehension: all_tasks = [task.to_dict() for task in goal.tasks] |
||
|
||
response = { | ||
"id": goal.id, | ||
"title": goal.title, | ||
"tasks": all_tasks | ||
} | ||
|
||
return response |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from flask import abort, make_response | ||
from ..db import db | ||
|
||
def validate_model(cls, model_id): | ||
try: | ||
model_id = int(model_id) | ||
except: | ||
abort(make_response({"message":f"{cls.__name__} id {(model_id)} invalid"}, 400)) | ||
|
||
query = db.select(cls).where(cls.id == model_id) | ||
model = db.session.scalar(query) | ||
|
||
if not model: | ||
abort(make_response({"message":f"{cls.__name__} {model_id} not found"},404)) | ||
|
||
return model |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1 +1,106 @@ | ||||||
from flask import Blueprint | ||||||
from flask import Blueprint,request,abort,make_response,Response | ||||||
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. It takes more time to read a line when we have to break up the text a bit ourselves, adding spaces between the imports makes a difference when reading through a lot of code.
Suggested change
|
||||||
from app.models.task import Task | ||||||
from ..db import db | ||||||
from datetime import date | ||||||
import os | ||||||
import requests | ||||||
from .route_utilities import validate_model | ||||||
|
||||||
tasks_bp = Blueprint("tasks_bp",__name__, url_prefix="/tasks") | ||||||
|
||||||
@tasks_bp.post("") | ||||||
def create_task(): | ||||||
request_body = request.get_json() | ||||||
|
||||||
try: | ||||||
new_task = Task.from_dict(request_body) | ||||||
|
||||||
except KeyError as error: | ||||||
response = {"details":f"Invalid data"} | ||||||
abort(make_response(response,400)) | ||||||
|
||||||
|
||||||
db.session.add(new_task) | ||||||
db.session.commit() | ||||||
|
||||||
response = {"task" : new_task.to_dict()} | ||||||
|
||||||
return response,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.
Suggested change
|
||||||
|
||||||
@tasks_bp.get("") | ||||||
def get_all_tasks(): | ||||||
|
||||||
query = db.select(Task) | ||||||
sorted_title = request.args.get("sort") | ||||||
if sorted_title == "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],200) | ||||||
|
||||||
return tasks_response | ||||||
|
||||||
|
||||||
@tasks_bp.get("/<task_id>") | ||||||
def get_single_task(task_id): | ||||||
task = validate_model(Task, task_id) | ||||||
|
||||||
return {"task" : task.to_dict()} | ||||||
|
||||||
@tasks_bp.put("/<task_id>") | ||||||
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 {"task" : task.to_dict()} | ||||||
|
||||||
@tasks_bp.delete("/<task_id>") | ||||||
def delete_task(task_id): | ||||||
task = validate_model(Task, task_id) | ||||||
|
||||||
db.session.delete(task) | ||||||
db.session.commit() | ||||||
|
||||||
return {"details": f'Task {task_id} "{task.title}" successfully deleted'} | ||||||
|
||||||
@tasks_bp.patch("/<task_id>/mark_complete") | ||||||
def partial_update_complete_one_task(task_id): | ||||||
task = validate_model(Task, task_id) | ||||||
|
||||||
task.completed_at = date.today() | ||||||
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, but the |
||||||
|
||||||
db.session.add(task) | ||||||
db.session.commit() | ||||||
|
||||||
message = f"Someone just completed the task My Beautiful {task.title}" | ||||||
|
||||||
slack_response = requests.post (f"https://slack.com/api/chat.postMessage?channel=C07V6E62DS7&text={message}",headers = {"Authorization":f"Bearer {os.environ.get('SLACK_AUTHENTICATION')}"}) | ||||||
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. Some tiny whitespace adjustments:
Suggested change
|
||||||
|
||||||
|
||||||
return {"task":task.to_dict()} | ||||||
|
||||||
@tasks_bp.patch("/<task_id>/mark_incomplete") | ||||||
def partial_update_incomplete_task(task_id): | ||||||
task= validate_model(Task, task_id) | ||||||
|
||||||
task.completed_at = None | ||||||
|
||||||
db.session.add(task) | ||||||
db.session.commit() | ||||||
|
||||||
return {"task":task.to_dict()} | ||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Single-database configuration for Flask. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# 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,flask_migrate | ||
|
||
[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 | ||
|
||
[logger_flask_migrate] | ||
level = INFO | ||
handlers = | ||
qualname = flask_migrate | ||
|
||
[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.
We're importing
goal
on line 4 and line 6; this import should be removed to keep our file tidy.