Skip to content

C22 updates and solution comparison #68

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: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Skills Assessed

- Following directions and reading comprehension
- Gathering technical requirements from written documentation
- Reading, writing, and using tests
- Demonstrating understanding of the client-server model, request-response cycle and conventional RESTful routes
- Driving development with independent research, experimentation, and collaboration
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 4 additions & 12 deletions ada-project-docs/optional-enhancements.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,6 @@ How would you write tests for it? How would you implement it?

Your decisions should not break the other tests.

### Re-organize Routes

Consider refactoring how endpoints are written in the `routes.py` file.

Here are some ideas to start:

- Instead of having `if/elif` blocks to handle many HTTP methods in one route method, separate them into different route methods

### Model Instance Methods

We can define instance methods in our model classes.
Expand All @@ -44,7 +36,7 @@ Consider places in your code that deal with one model at a time. Is there any re

Here are some ideas to start:

- Create an instance method in `Task` named `to_json()`
- Create an instance method in `Task` named `to_dict()`
- Converts a `Task` instance into JSON
- Returns a Python dictionary in the shape of the JSON our API returns in the `GET` `/tasks` route
- Create a class method in `Task` named `from_json()`
Expand All @@ -54,13 +46,13 @@ Here are some ideas to start:

### Use List Comprehensions

Use list comprehensions in your `routes.py` logic.
Use list comprehensions in your route functions where applicable.

### Route Helper Methods

If you have not already refactored your `routes.py` to use helper methods, do so now!
If you have not already refactored your route files to use helper methods, do so now!

Consider code with complex or repetitive logic, and refactor it into helper methods. Watch your `routes.py` file become cleaner and more readable!
Consider code with complex or repetitive logic, and refactor it into helper methods. Watch your route files become cleaner and more readable!

### More Query Params

Expand Down
6 changes: 3 additions & 3 deletions ada-project-docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The goal for setup is to cover all of the set up needed at the beginning of this
1. Setting up development and test databases
1. Setting up a `.env` file
1. Running `$ flask db init`
1. Running `$ flask run` and `$ FLASK_ENV=development flask run`
1. Running `$ flask run` and `$ flask run --debug`

# Requirements

Expand Down Expand Up @@ -63,14 +63,14 @@ Run `$ flask db init`.

**_After you make your first model in Wave 1_**, run the other commands `migrate` and `upgrade`.

## Run `$ flask run` or `$ FLASK_ENV=development flask run`
## Run `$ flask run` or `$ flask run --debug`

Check that your Flask server can run with `$ flask run`.

We can run the Flask server specifying that we're working in the development environment. This enables hot-reloading, which is a feature that refreshes the Flask server every time there is a detected change.

```bash
$ FLASK_ENV=development flask run
$ flask run --debug
```

**It is highly recommended to run the Flask servers with this command**.
58 changes: 25 additions & 33 deletions ada-project-docs/wave_01.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@ Tasks are entities that describe a task a user wants to complete. They contain a
- description to hold details about the task
- an optional datetime that the task is completed on

Our goal for this wave is to be able to create, read, update, and delete different tasks. We will create RESTful routes for this different operations.
Our goal for this wave is to be able to create, read, update, and delete different tasks. We will create RESTful routes for these different operations.

# Requirements

## Task Model

There should be a `Task` model that lives in `app/models/task.py`.

Tasks should contain these attributes. Feel free to change the name of the `task_id` column if you would like. **The tests require the remaining columns to be named exactly** as `title`, `description`, and `completed_at`.
Tasks should contain these attributes. **The tests require the following columns to be named exactly** as `title`, `description`, and `completed_at`.

- `task_id`: a primary key for each task
- `id`: a primary key for each task
- `title`: text to name the task
- `description`: text to describe the task
- `completed_at`: a datetime that has the date that a task is completed on. **Can be _nullable_,** and contain a null value. A task with a `null` value for `completed_at` has not been completed. When we create a new task, `completed_at` should be `null` AKA `None` in Python.
- `completed_at`: a datetime that represents the date that a task is completed on. **Can be _nullable_,** and contain a null value. A task with a `null` value for `completed_at` has not been completed. When we create a new task, `completed_at` should be `null` AKA `None` in Python.

### Tips

- SQLAlchemy's column type for text is `db.String`. The column type for datetime is `db.DateTime`.
- SQLAlchemy supports _nullable_ columns with specific syntax.
- To work with date information, we can import the `datetime` data type with the import line `from datetime import datetime`.
- SQLAlchemy supports optional, or _nullable_, columns with specific syntax.
- Don't forget to run:
- `flask db init` once during setup
- `flask db migrate` every time there's a change in models, in order to generate migrations
Expand All @@ -38,8 +38,6 @@ Tasks should contain these attributes. Feel free to change the name of the `task

## CRUD for Tasks

The following are required routes for wave 1. Feel free to implement the routes in any order within this wave.

### Tips

- Pay attention to the exact shape of the expected JSON. Double-check nested data structures and the names of the keys for any mispellings.
Expand All @@ -52,9 +50,15 @@ The following are required routes for wave 1. Feel free to implement the routes

### CLI

In addition to testing your code with pytest and postman, you can play test your code with the CLI (Command Line Interface) by running `python3 cli/main.py`. The flask server needs to be running to run the CLI.
In addition to testing your code with pytest and postman, you can play test your code with the CLI (Command Line Interface) by running `python3 cli/main.py`.

The flask server needs to be running first before running the CLI.

### CRUD Routes

### Create a Task: Valid Task With `null` `completed_at`
The following are required routes for wave 1. Feel free to implement the routes in any order within this wave.

#### Create a Task: Valid Task With `null` `completed_at`

As a client, I want to be able to make a `POST` request to `/tasks` with the following HTTP request body

Expand Down Expand Up @@ -83,7 +87,7 @@ and get this response:

so that I know I successfully created a Task that is saved in the database.

### Get Tasks: Getting Saved Tasks
#### Get Tasks: Getting Saved Tasks

As a client, I want to be able to make a `GET` request to `/tasks` when there is at least one saved task and get this response:

Expand All @@ -106,7 +110,7 @@ As a client, I want to be able to make a `GET` request to `/tasks` when there is
]
```

### Get Tasks: No Saved Tasks
#### Get Tasks: No Saved Tasks

As a client, I want to be able to make a `GET` request to `/tasks` when there are zero saved tasks and get this response:

Expand All @@ -116,7 +120,7 @@ As a client, I want to be able to make a `GET` request to `/tasks` when there ar
[]
```

### Get One Task: One Saved Task
#### Get One Task: One Saved Task

As a client, I want to be able to make a `GET` request to `/tasks/1` when there is at least one saved task and get this response:

Expand All @@ -133,7 +137,7 @@ As a client, I want to be able to make a `GET` request to `/tasks/1` when there
}
```

### Update Task
#### Update Task

As a client, I want to be able to make a `PUT` request to `/tasks/1` when there is at least one saved task with this request body:

Expand All @@ -159,9 +163,9 @@ and get this response:
}
```

Note that the update endpoint does update the `completed_at` attribute. This will be updated with custom endpoints implemented in Wave 03.
Note that the update endpoint does update the `completed_at` attribute. This will be updated with custom endpoints implemented in Wave 3.

### Delete Task: Deleting a Task
#### Delete Task: Deleting a Task

As a client, I want to be able to make a `DELETE` request to `/tasks/1` when there is at least one saved task and get this response:

Expand All @@ -173,15 +177,15 @@ As a client, I want to be able to make a `DELETE` request to `/tasks/1` when the
}
```

### No matching Task: Get, Update, and Delete
#### No Matching Task: Get, Update, and Delete

As a client, if I make any of the following requests:

* `GET` `/tasks/<task_id>`
* `UPDATE` `/tasks/<task_id>`
* `DELETE` `/tasks/<task_id>`

and there is no existing task with `task_id`
and there is no existing task with an `id` of `task_id`

The response code should be `404`.

Expand All @@ -190,9 +194,9 @@ You may choose the response body.
Make sure to complete the tests for non-existing tasks to check that the correct response body is returned.


### Create a Task: Invalid Task With Missing Data
#### Create a Task: Invalid Task With Missing Data

#### Missing `title`
##### Missing `title`

As a client, I want to be able to make a `POST` request to `/tasks` with the following HTTP request body

Expand All @@ -215,7 +219,7 @@ and get this response:

so that I know I did not create a Task that is saved in the database.

#### Missing `description`
##### Missing `description`

If the HTTP request is missing `description`, we should also get this response:

Expand All @@ -226,15 +230,3 @@ If the HTTP request is missing `description`, we should also get this response:
"details": "Invalid data"
}
```

#### Missing `completed_at`

If the HTTP request is missing `completed_at`, we should also get this response:

`400 Bad Request`

```json
{
"details": "Invalid data"
}
```
6 changes: 3 additions & 3 deletions ada-project-docs/wave_05.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ This wave requires more test writing.
- These tests are currently skipped with `@pytest.mark.skip(reason="test to be completed by student")` and the function body has `pass` in it. Once you implement these tests you should remove the `skip` decorator and the `pass`.
- For the tests you write, use the requirements in this document to guide your test writing.
- Pay attention to the exact shape of the expected JSON. Double-check nested data structures and the names of the keys for any mispellings.
- You can model your tests off of the Wave 01 tests for Tasks.
- You can model your tests off of the Wave 1 tests for Tasks.
- Some tests use a [fixture](https://docs.pytest.org/en/6.2.x/fixture.html) named `one_goal` that is defined in `tests/conftest.py`. This fixture saves a specific goal to the test database.


Expand All @@ -28,9 +28,9 @@ This wave requires more test writing.

There should be a `Goal` model that lives in `app/models/goal.py`.

Goals should contain these attributes. Feel free to change the name of the `goal_id` column if you would like. **The tests require the title column to be named exactly** as `title`.
Goals should contain these attributes. **The tests require the title column to be named exactly** as `title`.

- `goal_id`: a primary key for each goal
- `id`: a primary key for each goal
- `title`: text to name the goal

### Tips
Expand Down
14 changes: 4 additions & 10 deletions ada-project-docs/wave_06.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,26 @@ Secondly, we should create our new route, `/goals/<goal_id>/tasks`, so that our

### Tips

- Use independent research to discover how to set up a one-to-many relationship in Flask.
- Use lesson materials and independent research to review how to set up a one-to-many relationship in Flask.
- Remember to run `flask db migrate` and `flask db upgrade` whenever there is a change to the model.
- Pay attention to the exact shape of the expected JSON. Double-check nested data structures and the names of the keys for any mispellings.
- Use the tests in `tests/test_wave_06.py` to guide your implementation.
- Some tests use a fixture named `one_task_belongs_to_one_goal` that is defined in `tests/conftest.py`. This fixture saves a task and a goal to the test database, and uses SQLAlchemy to associate the goal and task together.

### Updates to the Goal Model

Use independent research to discover how to set up a one-to-many relationship in Flask.

The Goal model should have a _relationship_ with the model Task.

After learning the strategy for creating a one-to-many relationship, in the Goal model, we recommend:

- Setting the `lazy` value to `True`
After reviewing the strategy for creating a one-to-many relationship, it is up to you if you would like to add convenience attributes for accessing the `Goal` model from it's related `Task`s and vice versa, accessing the list of associated `Task`s from a `Goal` model.

### Updates to the Task Model

Use independent research to discover how to set up a one-to-many relationship in Flask.

The Task model should belong to one `Goal`.

After learning the strategy for creating a one-to-many relationship, in the Task model, we recommend:
After reviewing the strategy for creating a one-to-many relationship, in the Task model, we recommend:

- Setting the foreign key to `goal`'s primary key column
- Setting the `nullable` to `True`
- Using `Optional` syntax to make the attribute nullable

Remember to run `flask db migrate` and `flask db upgrade` whenever there is a change to the model.

Expand Down
10 changes: 9 additions & 1 deletion ada-project-docs/wave_07.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ Our goal is to make our project accessible online!

Deploy this project to Render.

Then, add some Task records and Goal records to the production database.
When deploying a web service to Render, it will try to be helpful and set the `Language` field for you, but it doesn't always select the correct option.

![A screen shot of Render's UI for deploying a web service with the Language field circled in red showing Docker selected](assets/render-show-language-field.png)

Our language for this project should be `Python 3`, which you can select from a drop down if needed by clicking on the current value of the `Language` field.

![A screen shot of Render's UI for deploying a web service showing the drop down for selecting a runtime value](assets/render-show-language-drop-down.png)

Once deployed, add some Task records and Goal records to the production database.

Be sure to grab the URL of your deployed app. It will be submitted at the time of project submission.

Expand Down
30 changes: 9 additions & 21 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,18 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from .db import db, migrate
from .models import task, goal
import os
from dotenv import load_dotenv


db = SQLAlchemy()
migrate = Migrate()
load_dotenv()


def create_app(test_config=None):
def create_app(config=None):
app = Flask(__name__)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

if test_config is None:
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_DATABASE_URI")
else:
app.config["TESTING"] = True
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_TEST_DATABASE_URI")
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('SQLALCHEMY_DATABASE_URI')

# Import models here for Alembic setup
from app.models.task import Task
from app.models.goal import Goal
if config:
# Merge `config` into the app's configuration
# to override the app's default settings for testing
app.config.update(config)

db.init_app(app)
migrate.init_app(app, db)
Expand Down
6 changes: 6 additions & 0 deletions app/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from .models.base import Base

db = SQLAlchemy(model_class=Base)
migrate = Migrate()
4 changes: 4 additions & 0 deletions app/models/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
pass
19 changes: 16 additions & 3 deletions app/models/goal.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
from app import db

from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db import db

class Goal(db.Model):
goal_id = db.Column(db.Integer, primary_key=True)
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 {
"id": self.id,
"title": self.title,
}

@classmethod
def from_dict(cls, goal_data):
new_goal = cls(title=goal_data["title"])
return new_goal
Loading