-
Notifications
You must be signed in to change notification settings - Fork 127
Angela Amethyst A19 #130
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?
Angela Amethyst A19 #130
Changes from all commits
494a30a
855de52
11b64be
17a07f9
daf2560
fb22ab7
f46514e
d6abbbd
cf1d1c1
43a69a2
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 |
---|---|---|
|
@@ -4,19 +4,20 @@ | |
import os | ||
from dotenv import load_dotenv | ||
|
||
|
||
db = SQLAlchemy() | ||
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. Do you remember what this does? |
||
migrate = Migrate() | ||
load_dotenv() | ||
Comment on lines
7
to
9
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. 🎉 |
||
|
||
|
||
def create_app(test_config=None): | ||
|
||
app = Flask(__name__) | ||
|
||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False | ||
|
||
if test_config is None: | ||
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get( | ||
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. Why do we need this line? |
||
"SQLALCHEMY_DATABASE_URI") | ||
"RENDER_DATABASE_URI") | ||
else: | ||
app.config["TESTING"] = True | ||
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get( | ||
|
@@ -30,5 +31,9 @@ def create_app(test_config=None): | |
migrate.init_app(app, db) | ||
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. Great job! We need to pass our instance of our app & the instance of our SQLALchemy db to connect the db and migrate to our Flask app |
||
|
||
# Register Blueprints here | ||
from .routes import goals_bp | ||
app.register_blueprint(goals_bp) | ||
from .routes import tasks_bp | ||
app.register_blueprint(tasks_bp) | ||
Comment on lines
33
to
+37
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. Great work registering these blueprints ✅ |
||
|
||
return app |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,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. Great job completing Task List Angela! Your code looks clean and easily readable. I want to point out the good variable naming and your code is DRY. Great use of helper methods in your models! Excellent work using blue prints & creating RESTful CRUD routes for each model. |
||
class Goal(db.Model): | ||
goal_id = db.Column(db.Integer, primary_key=True) | ||
title = db.Column(db.String) | ||
tasks = db.relationship("Task", back_populates="goal") | ||
|
||
def to_dict(self): | ||
goal_as_dict = { | ||
"id": self.goal_id, | ||
"title": self.title | ||
} | ||
|
||
return goal_as_dict | ||
Comment on lines
+9
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. Great job creating this |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,25 @@ | ||
from app import db | ||
|
||
|
||
class Task(db.Model): | ||
task_id = db.Column(db.Integer, primary_key=True) | ||
title = db.Column(db.String) | ||
description = db.Column(db.String) | ||
completed_at = db.Column(db.DateTime, nullable=True) | ||
goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'), nullable=True) | ||
goal = db.relationship("Goal", back_populates="tasks") | ||
Comment on lines
+8
to
+9
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. Nice approach creating this relationship to your Goal model. Why not use lazy here? |
||
|
||
def to_dict(self): | ||
task_as_dict = { | ||
"id": self.task_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": True if self.completed_at else False, | ||
|
||
} | ||
|
||
if self.goal_id: | ||
task_as_dict["goal_id"] = self.goal_id | ||
|
||
return task_as_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. Looks like there's some extra whitespace here |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,235 @@ | ||
from flask import Blueprint | ||
from flask import Flask, Blueprint, jsonify, abort, make_response, request | ||
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. 👍🏾 These imports help a great deal when it comes to our request & response cycle |
||
from app import db | ||
from app.models.goal import Goal | ||
from app.models.task import Task | ||
from datetime import date | ||
|
||
goals_bp = Blueprint("goals", __name__, url_prefix="/goals") | ||
tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") | ||
|
||
Comment on lines
+7
to
+9
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. 👍🏾 |
||
#TASK ROUTES | ||
|
||
def validate_model(cls, model_id): | ||
try: | ||
model_id = int(model_id) | ||
except: | ||
abort(make_response({"message": f"{model_id} is not a valid type ({type(model_id)}). Must be an integer)"}, 400)) | ||
|
||
model = cls.query.get(model_id) | ||
|
||
if not model: | ||
abort(make_response({"message": f"{cls.__name__} {model_id} does not exist"}, 404)) | ||
|
||
return model | ||
Comment on lines
+12
to
+23
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. Nice job creating this reusable function & keeping your code DRY. Nice approach to handling an invalid model 😁 |
||
|
||
### | ||
|
||
@tasks_bp.route("", methods=['POST']) | ||
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. 🎉 |
||
|
||
def create_task(): | ||
|
||
request_body = request.get_json() | ||
|
||
if "title" not in request_body or "description" not in request_body or "is_complete" == False: | ||
abort(make_response({"details": "Invalid data"}, 400)) | ||
|
||
new_task = Task(title=request_body["title"], | ||
description=request_body["description"]) | ||
|
||
if "completed_at" in request_body: | ||
new_task.completed_at=request_body["completed_at"] | ||
|
||
db.session.add(new_task) | ||
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. Great job using
|
||
db.session.commit() | ||
|
||
return { | ||
"task": new_task.to_dict() | ||
}, 201 | ||
|
||
@tasks_bp.route("", methods = ["GET"]) | ||
def read_all_tasks(): | ||
|
||
sort_query = request.args.get("sort") | ||
|
||
if sort_query == "asc": | ||
tasks = Task.query.order_by(Task.title.asc()).all() | ||
elif sort_query == "desc": | ||
tasks = Task.query.order_by(Task.title.desc()).all() | ||
else: | ||
tasks = Task.query.all() | ||
|
||
tasks_response = [] | ||
|
||
for task in tasks: | ||
tasks_response.append(task.to_dict()) | ||
|
||
return jsonify(tasks_response), 200 | ||
|
||
@tasks_bp.route("/<task_id>", methods = ["GET"]) | ||
def read_one_task(task_id): | ||
|
||
task = validate_model(Task, task_id) | ||
|
||
return jsonify({"task":task.to_dict()}), 200 | ||
|
||
|
||
@tasks_bp.route("/<task_id>", methods=["PUT"]) | ||
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"] | ||
|
||
db.session.add(task) | ||
db.session.commit() | ||
|
||
return jsonify({"task":task.to_dict()}), 200 | ||
|
||
@tasks_bp.route("/<task_id>", methods=["DELETE"]) | ||
def delete_task(task_id): | ||
task = validate_model(Task, task_id) | ||
|
||
db.session.delete(task) | ||
db.session.commit() | ||
|
||
return make_response({"details": f"Task {task_id} \"{task.title}\" successfully deleted"}) | ||
|
||
|
||
|
||
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||
def mark_task_complete(task_id): | ||
|
||
task = validate_model(Task, task_id) | ||
|
||
task.completed_at = date.today() | ||
|
||
db.session.add(task) | ||
db.session.commit() | ||
|
||
return jsonify({"task":task.to_dict()}), 200 | ||
|
||
@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||
def mark_task_incomplete(task_id): | ||
|
||
task = validate_model(Task, task_id) | ||
|
||
task.completed_at = None | ||
|
||
db.session.add(task) | ||
db.session.commit() | ||
|
||
return jsonify({"task":task.to_dict()}), 200 | ||
|
||
################################# GOAL ROUTES ####################################### | ||
|
||
@goals_bp.route("", methods=['POST']) | ||
|
||
def create_goal(): | ||
|
||
request_body = request.get_json() | ||
|
||
if "title" not in request_body: | ||
abort(make_response({"details": "Invalid data"}, 400)) | ||
|
||
new_goal = Goal(title=request_body["title"]) | ||
|
||
db.session.add(new_goal) | ||
db.session.commit() | ||
|
||
return { | ||
"goal": new_goal.to_dict() | ||
}, 201 | ||
Comment on lines
+142
to
+144
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. Usually a resource creation relates to a 201 response code 👍🏾 |
||
|
||
|
||
|
||
|
||
@goals_bp.route("", methods = ["GET"]) | ||
|
||
def read_all_goals(): | ||
goals = Goal.query.all() | ||
|
||
goals_response = [] | ||
|
||
for goal in goals: | ||
goals_response.append(goal.to_dict()) | ||
|
||
return jsonify(goals_response), 200 | ||
|
||
|
||
|
||
@goals_bp.route("/<goal_id>", methods = ["GET"]) | ||
def read_one_goal(goal_id): | ||
|
||
goal = validate_model(Goal, goal_id) | ||
|
||
return jsonify({"goal":goal.to_dict()}), 200 | ||
|
||
|
||
|
||
@goals_bp.route("/<goal_id>", methods=["PUT"]) | ||
def update_goal(goal_id): | ||
|
||
goal = validate_model(Goal, goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
goal.title = request_body["title"] | ||
|
||
db.session.add(goal) | ||
db.session.commit() | ||
|
||
return jsonify({"goal":goal.to_dict()}), 200 | ||
|
||
|
||
@goals_bp.route("/<goal_id>", methods=["DELETE"]) | ||
def delete_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
|
||
db.session.delete(goal) | ||
db.session.commit() | ||
|
||
return make_response({"details": f"Goal {goal_id} \"{goal.title}\" successfully deleted"}) | ||
|
||
############################## MANY TO MANY RELATIONSHIP ####################### | ||
@goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
def create_list_of_tasks(goal_id): | ||
print("PRINT") | ||
goal = validate_model(Goal, goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
for task_id in request_body["task_ids"]: | ||
task = validate_model(Task, task_id) | ||
if task not in goal.tasks: | ||
goal.tasks.append(task) | ||
|
||
|
||
task_ids=[] | ||
for task in goal.tasks: | ||
task_ids.append(task.task_id) | ||
|
||
db.session.commit() | ||
Comment on lines
+204
to
+214
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. ✅ |
||
|
||
return { | ||
"id": int(goal_id), | ||
"task_ids": task_ids }, 200 | ||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
|
||
def read_task_of_one_goal(goal_id): | ||
|
||
goal = validate_model(Goal, goal_id) | ||
|
||
tasks_response = [] | ||
|
||
for task in goal.tasks: | ||
tasks_response.append(task.to_dict()) | ||
|
||
return { | ||
"id": int(goal_id), | ||
"title": goal.title, | ||
"tasks": tasks_response | ||
}, 200 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Generic single-database configuration. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# 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 | ||
|
||
[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 | ||
|
||
[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.
Great work! We need to import load_dotenv to set up environment variables in our .env file