Skip to content

Commit 61c3168

Browse files
committed
wave5-7
1 parent bd34383 commit 61c3168

File tree

11 files changed

+301
-55
lines changed

11 files changed

+301
-55
lines changed

app/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .db import db, migrate
33
from .models import task, goal
44
from app.routes.task_routes import bp as tasks_bp
5+
from app.routes.goal_routes import bp as goals_bp
56
import os
67

78
def create_app(config=None):
@@ -20,5 +21,7 @@ def create_app(config=None):
2021

2122
# Register Blueprints here
2223
app.register_blueprint(tasks_bp)
24+
25+
app.register_blueprint(goals_bp)
2326

2427
return app

app/models/goal.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1-
from sqlalchemy.orm import Mapped, mapped_column
1+
from sqlalchemy.orm import Mapped, mapped_column,relationship
22
from ..db import db
3+
from sqlalchemy import String
34
#from sqlalchemy import Date
45

56

67
class Goal(db.Model):
8+
79
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
8-
10+
title: Mapped[str] = mapped_column(String(100), nullable=False)
11+
tasks: Mapped[list["Task"]] = relationship(
12+
back_populates="goal")
13+
14+
15+
def to_dict(self):
16+
return {
17+
"id": self.id,
18+
"title": self.title
19+
}
20+
21+
@classmethod
22+
def from_dict(cls, data_dict):
23+
if "title" not in data_dict:
24+
raise KeyError("title")
25+
return cls(title=data_dict["title"])

app/models/task.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,42 @@
11
from flask_sqlalchemy import SQLAlchemy
2-
from sqlalchemy.orm import Mapped, mapped_column
2+
from sqlalchemy.orm import Mapped, mapped_column,relationship
3+
from sqlalchemy import ForeignKey
34
from ..db import db
45
from sqlalchemy import String, Date
56
from datetime import datetime
67
from flask import Blueprint, abort, make_response, request, Response,jsonify
8+
from typing import Optional
79

810
class Task(db.Model):
911
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
1012
title: Mapped[str] = mapped_column(String(100), nullable=False)
1113
description: Mapped[str] = mapped_column(String(255))
1214
completed_at:Mapped[datetime.date] = mapped_column(Date, nullable=True)
1315

16+
goal_id: Mapped[Optional[int]] = mapped_column(ForeignKey("goal.id"))
17+
goal: Mapped[Optional["Goal"]] = relationship(back_populates="tasks")
1418

1519
def to_dict(self): # json
1620

1721

18-
return {"id": self.id,
22+
task_dict = {"id": self.id,
1923
"title":self.title,
2024
"description":self.description,
2125
#"is_complete":self.completed_at if self.completed_at else False
2226
"is_complete": self.completed_at is not None}
2327

28+
if self.goal_id is not None:
29+
task_dict["goal_id"] = self.goal_id
30+
31+
32+
return task_dict
33+
2434

2535

2636
@classmethod
2737
def from_dict(cls, data_dict):
2838
if "title" not in data_dict:
29-
#abort(make_response({"details": "Invalid data"}, 400))
39+
3040
raise KeyError("title")
3141
if "description" not in data_dict:
3242
#abort(make_response({"details": "Invalid data"}, 400))

app/routes/goal_routes.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,100 @@
1-
from flask import Blueprint
1+
from flask import Blueprint, jsonify, request, abort, make_response
2+
from ..db import db
3+
from ..models.goal import Goal
4+
from ..models.task import Task
5+
from .route_utilities import validate_model
6+
7+
8+
bp = Blueprint("goals_bp", __name__, url_prefix="/goals")
9+
10+
11+
12+
@bp.post("")
13+
def create_goal():
14+
request_body = request.get_json()
15+
try:
16+
new_goal = Goal.from_dict(request_body)
17+
except KeyError:
18+
return jsonify({"details": "Invalid data"}), 400
19+
20+
db.session.add(new_goal)
21+
db.session.commit()
22+
23+
return jsonify(new_goal.to_dict()), 201
24+
25+
26+
@bp.get("")
27+
def get_all_goals():
28+
goals = Goal.query.order_by(Goal.id).all()
29+
return jsonify([goal.to_dict() for goal in goals]), 200
30+
31+
32+
@bp.get("/<id>")
33+
def get_one_goal(id):
34+
goal = validate_model(Goal, id)
35+
return jsonify(goal.to_dict()), 200
36+
37+
38+
39+
40+
@bp.put("/<id>")
41+
def update_goal(id):
42+
goal = validate_model(Goal, id)
43+
request_body = request.get_json()
44+
45+
goal.title = request_body["title"]
46+
47+
db.session.commit()
48+
return jsonify(goal.to_dict()), 200
49+
50+
51+
52+
@bp.delete("/<id>")
53+
def delete_goal(id):
54+
goal = validate_model(Goal, id)
55+
56+
db.session.delete(goal)
57+
db.session.commit()
58+
59+
return jsonify({"message": f'Goal {goal.id} successfully deleted'}), 204
60+
61+
62+
#nested
63+
@bp.post("/<goal_id>/tasks")
64+
def add_tasks_to_goal(goal_id):
65+
goal = validate_model(Goal, goal_id)
66+
request_body = request.get_json()
67+
68+
task_ids = request_body.get("task_ids", [])
69+
70+
for task in goal.tasks:
71+
task.goal_id = None
72+
73+
74+
75+
for task_id in task_ids:
76+
task = validate_model(Task, task_id)
77+
task.goal_id = goal.id
78+
79+
db.session.commit()
80+
81+
return jsonify({
82+
"id": goal.id,
83+
"task_ids": task_ids
84+
}), 200
85+
86+
87+
88+
@bp.get("/<goal_id>/tasks")
89+
def get_tasks_for_goal(goal_id):
90+
goal = validate_model(Goal, goal_id)
91+
92+
tasks_response = [task.to_dict() for task in goal.tasks]
93+
94+
goal_dict = goal.to_dict()
95+
goal_dict["tasks"] = tasks_response
96+
97+
return jsonify(goal_dict), 200
98+
99+
100+
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ def create_model(cls, model_data):
2222
try:
2323
new_model = cls.from_dict(model_data)
2424
except KeyError as e:
25-
response = {"message": f"Invalid request: missing {e.args[0]}"}
26-
abort(make_response(response, 400))
25+
#response = {"message": f"Invalid request: missing {e.args[0]}"}
26+
#abort(make_response(response, 400))
27+
abort(make_response({"details": "Invalid data"}, 400))
2728

2829
db.session.add(new_model)
2930
db.session.commit()

app/routes/task_routes.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from ..models.task import Task
22
from flask import Blueprint, abort, make_response, request, Response,jsonify
33
from ..db import db
4-
from .routes_utilities import validate_model,create_model,get_models_with_filters
4+
from .route_utilities import validate_model,create_model,get_models_with_filters
55
from datetime import datetime
66

77
bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks")
@@ -14,7 +14,8 @@ def create_task():
1414
try:
1515
new_task = Task.from_dict(request_body)
1616
except KeyError:
17-
return jsonify({"details": "Invalid data"}), 400
17+
#return jsonify({"details": "Invalid data"}), 400
18+
abort(make_response({"details": "Invalid data"}, 400))
1819

1920
db.session.add(new_task)
2021
db.session.commit()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""empty message
2+
3+
Revision ID: 52388701adf4
4+
Revises: b73fa682b647
5+
Create Date: 2025-11-05 16:55:55.917780
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '52388701adf4'
14+
down_revision = 'b73fa682b647'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
with op.batch_alter_table('task', schema=None) as batch_op:
22+
batch_op.add_column(sa.Column('goal_id', sa.Integer(), nullable=True))
23+
batch_op.create_foreign_key(None, 'goal', ['goal_id'], ['id'])
24+
25+
# ### end Alembic commands ###
26+
27+
28+
def downgrade():
29+
# ### commands auto generated by Alembic - please adjust! ###
30+
with op.batch_alter_table('task', schema=None) as batch_op:
31+
batch_op.drop_constraint(None, type_='foreignkey')
32+
batch_op.drop_column('goal_id')
33+
34+
# ### end Alembic commands ###
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""empty message
2+
3+
Revision ID: b73fa682b647
4+
Revises: af48238dc931
5+
Create Date: 2025-11-05 16:50:04.362330
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'b73fa682b647'
14+
down_revision = 'af48238dc931'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
with op.batch_alter_table('goal', schema=None) as batch_op:
22+
batch_op.add_column(sa.Column('title', sa.String(length=100), nullable=False))
23+
24+
with op.batch_alter_table('task', schema=None) as batch_op:
25+
batch_op.add_column(sa.Column('title', sa.String(length=100), nullable=False))
26+
batch_op.add_column(sa.Column('description', sa.String(length=255), nullable=False))
27+
batch_op.add_column(sa.Column('completed_at', sa.Date(), nullable=True))
28+
29+
# ### end Alembic commands ###
30+
31+
32+
def downgrade():
33+
# ### commands auto generated by Alembic - please adjust! ###
34+
with op.batch_alter_table('task', schema=None) as batch_op:
35+
batch_op.drop_column('completed_at')
36+
batch_op.drop_column('description')
37+
batch_op.drop_column('title')
38+
39+
with op.batch_alter_table('goal', schema=None) as batch_op:
40+
batch_op.drop_column('title')
41+
42+
# ### end Alembic commands ###

0 commit comments

Comments
 (0)