Skip to content

Commit bd34383

Browse files
committed
wave1-3
1 parent bfab5cb commit bd34383

File tree

14 files changed

+466
-34
lines changed

14 files changed

+466
-34
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.vscode
22
.DS_Store
3+
.env
34

45
# Byte-compiled / optimized / DLL files
56
__pycache__/

app/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from flask import Flask
22
from .db import db, migrate
33
from .models import task, goal
4+
from app.routes.task_routes import bp as tasks_bp
45
import os
56

67
def create_app(config=None):
@@ -18,5 +19,6 @@ def create_app(config=None):
1819
migrate.init_app(app, db)
1920

2021
# Register Blueprints here
22+
app.register_blueprint(tasks_bp)
2123

2224
return app

app/models/goal.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from sqlalchemy.orm import Mapped, mapped_column
22
from ..db import db
3+
#from sqlalchemy import Date
4+
35

46
class Goal(db.Model):
57
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
8+

app/models/task.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
1+
from flask_sqlalchemy import SQLAlchemy
12
from sqlalchemy.orm import Mapped, mapped_column
23
from ..db import db
4+
from sqlalchemy import String, Date
5+
from datetime import datetime
6+
from flask import Blueprint, abort, make_response, request, Response,jsonify
37

48
class Task(db.Model):
59
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
10+
title: Mapped[str] = mapped_column(String(100), nullable=False)
11+
description: Mapped[str] = mapped_column(String(255))
12+
completed_at:Mapped[datetime.date] = mapped_column(Date, nullable=True)
13+
14+
15+
def to_dict(self): # json
16+
17+
18+
return {"id": self.id,
19+
"title":self.title,
20+
"description":self.description,
21+
#"is_complete":self.completed_at if self.completed_at else False
22+
"is_complete": self.completed_at is not None}
23+
24+
25+
26+
@classmethod
27+
def from_dict(cls, data_dict):
28+
if "title" not in data_dict:
29+
#abort(make_response({"details": "Invalid data"}, 400))
30+
raise KeyError("title")
31+
if "description" not in data_dict:
32+
#abort(make_response({"details": "Invalid data"}, 400))
33+
raise KeyError("description")
34+
return cls(
35+
title=data_dict["title"],
36+
description=data_dict["description"],
37+
completed_at=None # default to None when creating
38+
)
39+
40+
41+

app/routes/routes_utilities.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from flask import abort, make_response
2+
from ..db import db
3+
4+
def validate_model(cls, id):
5+
try:
6+
id = int(id)
7+
except ValueError:
8+
invalid = {"message": f"{cls.__name__} id({id}) is invalid."}
9+
abort(make_response(invalid, 400))
10+
11+
query = db.select(cls).where(cls.id == id)
12+
model = db.session.scalar(query)
13+
14+
if not model:
15+
not_found = {"message": f"{cls.__name__} with id({id}) not found."}
16+
abort(make_response(not_found, 404))
17+
18+
return model
19+
20+
21+
def create_model(cls, model_data):
22+
try:
23+
new_model = cls.from_dict(model_data)
24+
except KeyError as e:
25+
response = {"message": f"Invalid request: missing {e.args[0]}"}
26+
abort(make_response(response, 400))
27+
28+
db.session.add(new_model)
29+
db.session.commit()
30+
31+
return new_model.to_dict(), 201
32+
33+
def get_models_with_filters(cls, filters=None):
34+
query = db.select(cls)
35+
36+
if filters:
37+
for attribute, value in filters.items():
38+
if hasattr(cls, attribute):
39+
query = query.where(getattr(cls, attribute).ilike(f"%{value}%"))
40+
41+
models = db.session.scalars(query.order_by(cls.id))
42+
models_response = [model.to_dict() for model in models]
43+
return models_response

app/routes/task_routes.py

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,120 @@
1-
from flask import Blueprint
1+
from ..models.task import Task
2+
from flask import Blueprint, abort, make_response, request, Response,jsonify
3+
from ..db import db
4+
from .routes_utilities import validate_model,create_model,get_models_with_filters
5+
from datetime import datetime
6+
7+
bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks")
8+
9+
@bp.post("")
10+
def create_task():
11+
12+
request_body = request.get_json()
13+
14+
try:
15+
new_task = Task.from_dict(request_body)
16+
except KeyError:
17+
return jsonify({"details": "Invalid data"}), 400
18+
19+
db.session.add(new_task)
20+
db.session.commit()
21+
return new_task.to_dict(), 201
22+
23+
24+
@bp.get("")
25+
26+
def get_all_tasks():
27+
query = db.select(Task)
28+
29+
id_param = request.args.get("id")
30+
if id_param:
31+
query = query.where(Task.id == int(id_param))
32+
33+
description_param = request.args.get("description")
34+
if description_param:
35+
query = query.where(Task.description.ilike(f"%{description_param}%"))
36+
37+
title_param = request.args.get("title")
38+
sort_param = request.args.get("sort")
39+
if title_param:
40+
query = query.where(Task.title.ilike(f"%{title_param}%"))
41+
42+
complete_param = request.args.get("is_complete")
43+
if complete_param == "true":
44+
45+
query = query.where(Task.completed_at.is_not(None))
46+
47+
elif complete_param == "false":
48+
49+
query = query.where(Task.completed_at.is_(None))
50+
51+
#query = query.order_by(Task.id)
52+
if sort_param =="asc":
53+
query = query = query.order_by(Task.title.asc())
54+
elif sort_param == "desc":
55+
query = query.order_by(Task.title.desc())
56+
else:
57+
query = query.order_by(Task.id)
58+
tasks = db.session.scalars(query)
59+
60+
result_list = []
61+
62+
result_list =[task.to_dict()for task in tasks]
63+
64+
return jsonify(result_list or []),200
65+
66+
67+
@bp.get("/<id>")
68+
69+
def get_one_task(id):
70+
71+
task =validate_model(Task,id)
72+
return task.to_dict()
73+
74+
75+
@bp.put("/<id>")
76+
def replace_task(id):
77+
task = validate_model(Task, id)
78+
79+
request_body = request.get_json()
80+
81+
# if request_body["is_complete"]:
82+
# task.completed_at = datetime.utcnow()
83+
# else:
84+
# task.completed_at = None
85+
task.title = request_body["title"]
86+
87+
task.description = request_body["description"]
88+
89+
db.session.commit()
90+
91+
return Response(status = 204,mimetype ="application/json")
92+
93+
@bp.delete("/<id>")
94+
def del_task(id):
95+
task = validate_model(Task, id)
96+
97+
db.session.delete(task)
98+
99+
db.session.commit()
100+
101+
return Response(status = 204,mimetype ="application/json")
102+
103+
104+
105+
106+
@bp.patch("/<id>/mark_complete")
107+
def mark_complete(id):
108+
task = validate_model(Task, id)
109+
task.completed_at = datetime.utcnow()
110+
db.session.commit()
111+
return Response(status=204, mimetype="application/json")
112+
113+
114+
@bp.patch("/<id>/mark_incomplete")
115+
def mark_incomplete(id):
116+
task = validate_model(Task, id)
117+
118+
task.completed_at = None
119+
db.session.commit()
120+
return Response(status=204, mimetype="application/json")

migrations/README

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Single-database configuration for Flask.

migrations/alembic.ini

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# template used to generate migration files
5+
# file_template = %%(rev)s_%%(slug)s
6+
7+
# set to 'true' to run the environment during
8+
# the 'revision' command, regardless of autogenerate
9+
# revision_environment = false
10+
11+
12+
# Logging configuration
13+
[loggers]
14+
keys = root,sqlalchemy,alembic,flask_migrate
15+
16+
[handlers]
17+
keys = console
18+
19+
[formatters]
20+
keys = generic
21+
22+
[logger_root]
23+
level = WARN
24+
handlers = console
25+
qualname =
26+
27+
[logger_sqlalchemy]
28+
level = WARN
29+
handlers =
30+
qualname = sqlalchemy.engine
31+
32+
[logger_alembic]
33+
level = INFO
34+
handlers =
35+
qualname = alembic
36+
37+
[logger_flask_migrate]
38+
level = INFO
39+
handlers =
40+
qualname = flask_migrate
41+
42+
[handler_console]
43+
class = StreamHandler
44+
args = (sys.stderr,)
45+
level = NOTSET
46+
formatter = generic
47+
48+
[formatter_generic]
49+
format = %(levelname)-5.5s [%(name)s] %(message)s
50+
datefmt = %H:%M:%S

0 commit comments

Comments
 (0)