Skip to content

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from flask import Flask
from .db import db, migrate
from .models import task, goal
from .routes.task_routes import tasks_bp
from .routes.goal_routes import bp as goals_bp
from .models import goal
Copy link
Collaborator

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.

import os

def create_app(config=None):
Expand All @@ -18,5 +21,8 @@ def create_app(config=None):
migrate.init_app(app, db)

# Register Blueprints here
app.register_blueprint(tasks_bp)
app.register_blueprint(goals_bp)


return app
14 changes: 13 additions & 1 deletion app/models/goal.py
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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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"])
40 changes: 39 additions & 1 deletion app/models/task.py
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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
is_complete_task= False
is_complete_task = False

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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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)

)



107 changes: 106 additions & 1 deletion app/routes/goal_routes.py
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):
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
16 changes: 16 additions & 0 deletions app/routes/route_utilities.py
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
107 changes: 106 additions & 1 deletion app/routes/task_routes.py
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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 flask import Blueprint,request,abort,make_response,Response
from flask import Blueprint, request, abort, make_response, Response

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return response,201
return response, 201


@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()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, but the datetime module also has a method .now() which can optionally accept a timezone parameter. In the Python documentation now is noted as preferred over today (documentation)


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')}"})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some tiny whitespace adjustments:

  • we want to make sure to remove any space between a function and it's arguments when invoking it
  • one of the only times we do not surround a = operator with spaces is when we pass key-value arguments into a function
Suggested change
slack_response = requests.post (f"https://slack.com/api/chat.postMessage?channel=C07V6E62DS7&text={message}",headers = {"Authorization":f"Bearer {os.environ.get('SLACK_AUTHENTICATION')}"})
slack_response = requests.post(f"https://slack.com/api/chat.postMessage?channel=C07V6E62DS7&text={message}", headers={"Authorization":f"Bearer {os.environ.get('SLACK_AUTHENTICATION')}"})



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()}







1 change: 1 addition & 0 deletions migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Single-database configuration for Flask.
50 changes: 50 additions & 0 deletions migrations/alembic.ini
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
Loading