-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #51 from tecladocode/jose/cou-103-write-sqlalchemy…
…-many-to-many-seection
- Loading branch information
Showing
98 changed files
with
2,562 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
docs/docs/07_sqlalchemy_many_to_many/01_section_changes/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
--- | ||
title: Changes in this section | ||
description: In this section we add Tags to our Stores, and link these to Items using a many-to-many relationship. | ||
--- | ||
|
||
# Changes in this section | ||
|
||
It's common for online stores to use "tags" to group items and to be able to search for them a bit more easily. | ||
|
||
For example, an item "Chair" could be tagged with "Furniture" and "Office". | ||
|
||
Another item, "Laptop", could be tagged with "Tech" and "Office". | ||
|
||
So one item can be associated with many tags, and one tag can be associated with many items. | ||
|
||
This is a many-to-many relationship, which is bit trickier to implement than the one-to-many we've already implemented between Items and Stores. | ||
|
||
## When you have many stores | ||
|
||
We want to add one more constraint to tags, however. That is that if we have many stores, it's possible each store wants to use different tags. So the tags we create will be unique to each store. | ||
|
||
This means that tags will have: | ||
|
||
- A many-to-one relationship with stores | ||
- A many-to-many relationship with items | ||
|
||
Here's a diagram to illustrate what this looks like: | ||
|
||
![ER database model showing relationships](./assets/db_model.drawio.png) | ||
|
||
## New API endpoints to be added | ||
|
||
In this section we will add all the Tag endpoints: | ||
|
||
|
||
| Method | Endpoint | Description | | ||
| -------- | ----------------------- | ------------------------------------------------------- | | ||
| `GET` | `/stores/{id}/tags` | Get a list of tags in a store. | | ||
| `POST` | `/stores/{id}/tags` | Create a new tag. | | ||
| `POST` | `/items/{id}/tags/{id}` | Link an item in a store with a tag from the same store. | | ||
| `DELETE` | `/items/{id}/tags/{id}` | Unlink a tag from an item. | | ||
| `GET` | `/tags/{id}` | Get information about a tag given its unique id. | | ||
| `DELETE` | `/tags/{id}` | Delete a tag, which must have no associated items. | |
Binary file added
BIN
+113 KB
docs/docs/07_sqlalchemy_many_to_many/01_section_changes/assets/db_model.drawio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
195 changes: 195 additions & 0 deletions
195
docs/docs/07_sqlalchemy_many_to_many/02_one_to_many_review/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
--- | ||
title: One-to-many relationships review | ||
description: A super-quick look at creating the Tag model and setting up the one-to-many relationship with Stores. | ||
--- | ||
|
||
- [x] Set metadata above | ||
- [x] Start writing! | ||
- [x] Create `start` folder | ||
- [x] Create `end` folder | ||
- [ ] Create per-file diff between `end` and `start` (use "Compare Folders") | ||
|
||
# One-to-many relationship between Tag and Store | ||
|
||
Since we've already learned how to set up one-to-many relationships with SQLAlchemy when we looked at Items and Stores, let's go quickly in this section. | ||
|
||
## The SQLAlchemy models | ||
|
||
import Tabs from '@theme/Tabs'; | ||
import TabItem from '@theme/TabItem'; | ||
|
||
<div className="codeTabContainer"> | ||
<Tabs> | ||
<TabItem value="tag" label="models/tag.py" default> | ||
|
||
```python title="models/tag.py" | ||
from db import db | ||
|
||
|
||
class TagModel(db.Model): | ||
__tablename__ = "tags" | ||
|
||
id = db.Column(db.Integer, primary_key=True) | ||
name = db.Column(db.String(80), unique=True, nullable=False) | ||
store_id = db.Column(db.String(), db.ForeignKey("stores.id"), nullable=False) | ||
|
||
store = db.relationship("StoreModel", back_populates="tags") | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="store" label="models/store.py"> | ||
|
||
```python title="models/store.py" | ||
from db import db | ||
|
||
|
||
class StoreModel(db.Model): | ||
__tablename__ = "stores" | ||
|
||
id = db.Column(db.Integer, primary_key=True) | ||
name = db.Column(db.String(80), unique=True, nullable=False) | ||
|
||
items = db.relationship("ItemModel", back_populates="store", lazy="dynamic") | ||
# highlight-start | ||
tags = db.relationship("TagModel", back_populates="store", lazy="dynamic") | ||
# highlight-end | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
</div> | ||
|
||
## The marshmallow schemas | ||
|
||
These are the new schemas we'll add. Note that none of the tag schemas have any notion of "items". We'll add those to the schemas when we construct the many-to-many relationship. | ||
|
||
In the `StoreSchema` we add a new list field for the nested `PlainTagSchema`, just as it has with `PlainItemSchema`. | ||
|
||
```python title="schemas.py" | ||
class PlainTagSchema(Schema): | ||
id = fields.Int(dump_only=True) | ||
name = fields.Str() | ||
|
||
|
||
class StoreSchema(PlainStoreSchema): | ||
items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True) | ||
# highlight-start | ||
tags = fields.List(fields.Nested(PlainTagSchema()), dump_only=True) | ||
# highlight-end | ||
|
||
|
||
class TagSchema(PlainTagSchema): | ||
store_id = fields.Int(load_only=True) | ||
store = fields.Nested(PlainStoreSchema(), dump_only=True) | ||
``` | ||
|
||
## The API endpoints | ||
|
||
Let's add the Tag endpoints that aren't related to Items: | ||
|
||
|
||
| Method | Endpoint | Description | | ||
| ---------- | ----------------------- | ------------------------------------------------------- | | ||
| ✅ `GET` | `/stores/{id}/tags` | Get a list of tags in a store. | | ||
| ✅ `POST` | `/stores/{id}/tags` | Create a new tag. | | ||
| ❌ `POST` | `/items/{id}/tags/{id}` | Link an item in a store with a tag from the same store. | | ||
| ❌ `DELETE` | `/items/{id}/tags/{id}` | Unlink a tag from an item. | | ||
| ✅ `GET` | `/tags/{id}` | Get information about a tag given its unique id. | | ||
| ❌ `DELETE` | `/tags/{id}` | Delete a tag, which must have no associated items. | | ||
|
||
Here's the code we need to write to add these endpoints: | ||
|
||
```python title="resources/tag.py" | ||
from flask.views import MethodView | ||
from flask_smorest import Blueprint, abort | ||
from sqlalchemy.exc import SQLAlchemyError | ||
|
||
from db import db | ||
from models import TagModel, StoreModel | ||
from schemas import TagSchema | ||
|
||
blp = Blueprint("Tags", "tags", description="Operations on tags") | ||
|
||
|
||
@blp.route("/stores/<string:store_id>/tags") | ||
class TagsInStore(MethodView): | ||
@blp.response(200, TagSchema(many=True)) | ||
def get(self, store_id): | ||
store = StoreModel.query.get_or_404(store_id) | ||
|
||
return store.tags.all() | ||
|
||
@blp.arguments(TagSchema) | ||
@blp.response(201, TagSchema) | ||
def post(self, tag_data, store_id): | ||
if TagModel.query.filter(TagModel.store_id == store_id).first(): | ||
abort(400, message="A tag with that name already exists in that store.") | ||
|
||
tag = TagModel(**tag_data, store_id=store_id) | ||
|
||
try: | ||
db.session.add(tag) | ||
db.session.commit() | ||
except SQLAlchemyError as e: | ||
abort( | ||
500, | ||
message=str(e), | ||
) | ||
|
||
return tag | ||
|
||
|
||
@blp.route("/tags/<string:tag_id>") | ||
class Tag(MethodView): | ||
@blp.response(200, TagSchema) | ||
def get(self, tag_id): | ||
tag = TagModel.query.get_or_404(tag_id) | ||
return tag | ||
``` | ||
|
||
## Register the Tag blueprint in `app.py` | ||
|
||
Finally, we need to remember to import the blueprint and register it! | ||
|
||
```python title="app.py" | ||
from flask import Flask | ||
from flask_smorest import Api | ||
|
||
import models | ||
|
||
from db import db | ||
from resources.item import blp as ItemBlueprint | ||
from resources.store import blp as StoreBlueprint | ||
# highlight-start | ||
from resources.tag import blp as TagBlueprint | ||
# highlight-end | ||
|
||
|
||
def create_app(db_url=None): | ||
app = Flask(__name__) | ||
app.config["API_TITLE"] = "Stores REST API" | ||
app.config["API_VERSION"] = "v1" | ||
app.config["OPENAPI_VERSION"] = "3.0.3" | ||
app.config["OPENAPI_URL_PREFIX"] = "/" | ||
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui" | ||
app.config[ | ||
"OPENAPI_SWAGGER_UI_URL" | ||
] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/" | ||
app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db" | ||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False | ||
app.config["PROPAGATE_EXCEPTIONS"] = True | ||
db.init_app(app) | ||
api = Api(app) | ||
|
||
@app.before_first_request | ||
def create_tables(): | ||
db.create_all() | ||
|
||
api.register_blueprint(ItemBlueprint) | ||
api.register_blueprint(StoreBlueprint) | ||
# highlight-start | ||
api.register_blueprint(TagBlueprint) | ||
# highlight-end | ||
|
||
return app | ||
``` |
2 changes: 2 additions & 0 deletions
2
docs/docs/07_sqlalchemy_many_to_many/02_one_to_many_review/end/.flaskenv
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
FLASK_APP=app | ||
FLASK_ENV=development |
7 changes: 7 additions & 0 deletions
7
docs/docs/07_sqlalchemy_many_to_many/02_one_to_many_review/end/Dockerfile
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
FROM python:3.10 | ||
EXPOSE 5000 | ||
WORKDIR /app | ||
COPY ./requirements.txt requirements.txt | ||
RUN pip install --no-cache-dir --upgrade -r requirements.txt | ||
COPY . . | ||
CMD ["flask", "run", "--host", "0.0.0.0"] |
Oops, something went wrong.
81c4c5a
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.
Successfully deployed to the following URLs:
rest-apis-flask-python – ./
rest-apis-flask-python-git-develop-teclado.vercel.app
rest-apis-flask-python.vercel.app
rest-apis-flask-python-teclado.vercel.app