Skip to main content

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​

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=False, nullable=False)
store_id = db.Column(db.Integer, db.ForeignKey("stores.id"), nullable=False)

store = db.relationship("StoreModel", back_populates="tags")

Remember to import the TagModel in models/__init__.py so that it is then imported by app.py. Otherwise SQLAlchemy won't know about it, and it won't be able to create the tables.

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.

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)
tags = fields.List(fields.Nested(PlainTagSchema()), dump_only=True)


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:

MethodEndpointDescription
✅ GET/store/{id}/tagGet a list of tags in a store.
✅ POST/store/{id}/tagCreate a new tag.
❌ POST/item/{id}/tag/{id}Link an item in a store with a tag from the same store.
❌ DELETE/item/{id}/tag/{id}Unlink a tag from an item.
✅ GET/tag/{id}Get information about a tag given its unique id.
❌ DELETE/tag/{id}Delete a tag, which must have no associated items.

Here's the code we need to write to add these endpoints:

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("/store/<string:store_id>/tag")
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, TagModel.name == tag_data["name"]).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("/tag/<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!

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
from resources.tag import blp as TagBlueprint


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)

with app.app_context():
db.create_all()

api.register_blueprint(ItemBlueprint)
api.register_blueprint(StoreBlueprint)
api.register_blueprint(TagBlueprint)

return app