Skip to content

Accelerate - Risha A. #13

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 13 commits into
base: master
Choose a base branch
from
Open

Accelerate - Risha A. #13

wants to merge 13 commits into from

Conversation

rishallen
Copy link

No description provided.

Copy link

@anselrognlie anselrognlie left a comment

Choose a reason for hiding this comment

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

Nice job!

Waves 1 through 4 all look good. The tests pass, and the code is clear and understandable. The main thing in these waves to look at is how to reduce the repetitive code we need to write for CRUD operations on our models. There is a lot of similarity in input handling and validation, as well as the output that could be moved either to helper functions in the routes file, or into the model class itself.

Wave 5 was really close to being done. The main issue was with the method in goal for outputting the model as a dict. There was a small issue in the method itself, and the routes were calling the wrong name.

Wave 6 needed conditional goal_id handling in the task output, which you were really close on with the to_json method in Task, then adding tasks to goals and displaying tasks for goals would finish it off. You had the relationship set up in Task and Goal in a way that would work (one of the helper methods added by sSqlAlchemy would have had a misleading name, but the relationship itself was fine, including the foreign key relationship).

Try revisiting this, as most projects we work on will have more than a single table, and getting comfortable with these relationships will be really useful. You could also take a look at the retro-video-store project we started live coding to think about an example of a many-to-many relationship, which is also a common need in applications.

So I'd look at the feedback here, and see if you can get those last few bits working, since this will be helpful going forward.

Overall, good work.

Comment on lines +33 to +36
from .routes import task_bp
from .routes import goals_bp
app.register_blueprint(task_bp)
app.register_blueprint(goals_bp)

Choose a reason for hiding this comment

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

👍

One thing we started to touch on in the video store live code was that we can split routes into multiple files. We can make a routes folder, and put routes for each endpoint into separate files, named for their model. Then we can use the name bp for the blueprint in each file since it would be the only blueprint in the file. Then these imports might look like:

    from .routes import task, goal
    app.register_blueprint(task.bp)
    app.register_blueprint(goal.bp)

@@ -1,6 +1,14 @@
from flask import current_app
from app import db

from app.models.task import Task

Choose a reason for hiding this comment

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

We don't need to import the Task class name here. We only use the string "Task" in the relationship declaration. We only need to import Task if we actually use the Task constructor function here in this file.


class Goal(db.Model):
goal_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
tasks = db.relationship("Task", backref="task", lazy=True)

Choose a reason for hiding this comment

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

The backref argument is used to provide a name that SqlAlchemy will use to add a property to the target model (Task in this case) to get back to the current model Goal. goal would be an appropriate name.

Then, if we have a Task instance, we could write task.goal to get a reference to the Goal with which the Task is associated (or None if the Task isn't associated with a Goal).

The tasks association itself would let us write goal.tasks to get the collection of all the Tasks associated with a particular Goal.

Suggested change
tasks = db.relationship("Task", backref="task", lazy=True)
tasks = db.relationship("Task", backref="goal", lazy=True)

Comment on lines +10 to +14
def goal_json(self):
return( {
"id": self.goal_id,
"title": self.title
}, 200)

Choose a reason for hiding this comment

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

This method is the main reason wave 5 has some failures. Your routes logic is trying to call a method called goal_to_json but the method here is called goal_json. Also, we should only be returning the dictionary here, not the status code.

The following change would allow Wave 5 to pass!

Suggested change
def goal_json(self):
return( {
"id": self.goal_id,
"title": self.title
}, 200)
def goal_to_json(self):
return {
"id": self.goal_id,
"title": self.title
}

@@ -1,6 +1,29 @@
from flask import current_app
from app import db

# from app.models.goal import Goal

Choose a reason for hiding this comment

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

This line is not necessary (it's commented, so you probably noticed that) 😄

We only need to import thee Goal identifier if we literally use the Goal constructor in this file. Even if we had a parameter that received a Goal, we wouldn't need to import the Goal constructor!

Additionally. since there was an import in goal.py for task.py, this import would cause a circular import dependency. Avoiding circular dependencies is why we have imports in strange places in app/__init__.py too!


# goals route
@goals_bp.route("", methods = ["POST", "GET"], strict_slashes = False)
def handle_goals():

Choose a reason for hiding this comment

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

Similar feedback about splitting these functions, and moving validation and dictionary-handling logic around that I made for Task will also apply to Goal. These are common themes for any model with CRUD operations.

db.session.add(new_goal)
db.session.commit()
response_body = {
"goal": new_goal.goal_to_json()

Choose a reason for hiding this comment

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

The main issue with wave 5 was that your equivalent "to json" method was defined with the name goal_json instead. With the updated version of goal_to_json provided earlier, this should start working!

task_ids = request_body["task_ids"]

for task_id in task_ids:
goal_id.task_id = goal_id

Choose a reason for hiding this comment

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

goal_id is a simple string (of read directly from query params) or number, so it doesn't have a field called task_id. Even a Goal instance (like goal) wouldn't. A Goal has the field tasks (created by our relationship code) to which we can append Task instances (not IDs). On the other side of the relationship each Task instance has a field called goal_id that can be used to associate a Task with a Goal. With the changes suggested in Goal, they also have a field called goal to which we can assign an actual Goal instance.

For any of these cases, we really need to lookup each passed task id to get an instance, then either set the goal/goal_id on each task, or update the tasks collection on the goal. Something like

            task = Task.query.get_or_404(task_id)
            task.goal = goal

This wouldn't remove any existing tasks from the goal (we should think about what to do for those tasks).

Additionally, to ensure that the change to the relationships occur transactionally (either they all apply, or none of them do), we can move the commit outside the loop, so that it gets called once at the end.


db.session.commit()

return {"id":(goal_id), "task_ids": task_ids},200

Choose a reason for hiding this comment

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

The test is expecting a numeric version of the goal id, so we can get the version stored in the goal itself (or force the conversion with int).

Suggested change
return {"id":(goal_id), "task_ids": task_ids},200
return {"id":(goal.goal_id), "task_ids": task_ids},200


db.session.commit()

return {"id":(goal_id), "task_ids": task_ids},200

Choose a reason for hiding this comment

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

The last bit that would be needed for wave 6 would be to handle GET requests for the tasks of a goal. Something like the following would be a good start.

    else if request.method == "GET":
        tasks = []
        for task in goal.tasks:
            tasks.append(task.to_json())

        return {
            "id": goal.goal_id, 
            "title": goal.title,
            "tasks": tasks
        }, 200

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants