FastAPI + Tortoise ORM + FastAPI 用户 (Python) - 关系 - 多对多
FastAPI + Tortoise ORM + FastAPI Users (Python) - Relationship - Many To Many
我正在使用 FastAPI、Tortoise ORM 和 FastAPI 用户制作一个 API 来学习。基本上我从 OpenWeather API 获取城市天气数据并使用 Tortoise ORM 存储在 SQLite 数据库中。我有 FastAPI 用户的身份验证。
我使用的是外键,关系运行良好。但现在我想要一些改变(改进)。我希望每个用户都有自己的物品,在登录并访问端点 (@router.get("/me/onlyitems/", response_model=List[schemas.Card_Pydantic])
) 后,它只收到他们的物品。
工作正常,我只收到用户项目(使用 owner_id),但我的项目 table 使用外部唯一 ID(项目是一个城市,每个城市都有一个唯一的ID),它是主键。
但是,当另一个用户尝试添加同一个城市时,它会给出一个错误,因为它不能在 table 中拥有同一个城市,即使是另一个所有者。好吧,应该是这样……但我想让其他用户访问同一个城市(他们有相同的信息,他们没有定制)。
然后,我希望许多用户可以访问同一项目(并且每个用户可以访问多个项目)。
我不认为为每个用户重复项目信息(使用另一个 ID)是理想的,不同的用户访问相同的项目会更好。
我认为这将是一个多对多关系,但我不知道如何建立关系并使用 FastAPI 获得它。
我想过创建一个中介table,只有UserID和CityID(和一个自增ID),然后用它来获取相关数据,但我不知道这是不是最好的方法或者有可能。
我试图从 Tortoise 文档中学习,但我找不到我需要的部分或理解一些复杂的示例和部分文档。
我正在使用软件包的更新版本和 Python 3.9.4
好吧,让我展示一些代码:
models.py
class UserModel(TortoiseBaseUserModel): # From FastAPI Users
cards: fields.ReverseRelation["Card"]
class OAuthAccountModel(TortoiseBaseOAuthAccountModel): # From FastAPI Users
user = fields.ForeignKeyField("models.UserModel", related_name="oauth_accounts")
class User(models.BaseUser, models.BaseOAuthAccountMixin): # From FastAPI Users
pass
class UserCreate(models.BaseUserCreate): # From FastAPI Users
pass
class UserUpdate(User, models.BaseUserUpdate): # From FastAPI Users
pass
class UserDB(User, models.BaseUserDB, PydanticModel): # From FastAPI Users
class Config:
orm_mode = True
orig_model = UserModel
user_db = TortoiseUserDatabase(UserDB, UserModel, OAuthAccountModel) # From FastAPI Users
class Card(tmodels.Model):
"""
The Card model, related to the user.
"""
id = fields.IntField(pk=True)
city_name = fields.CharField(max_length=30)
temperature = fields.FloatField()
state = fields.CharField(max_length=30, null=True)
icon = fields.CharField(max_length=3, null=True)
period = fields.CharField(max_length=20, null=True)
created_at = fields.DatetimeField(auto_now_add=True)
updated_at = fields.DatetimeField(auto_now=True)
owner: fields.ForeignKeyRelation[UserModel] = fields.ForeignKeyField(
"models.UserModel", related_name="cards")
class PydanticMeta:
exclude = ["owner"]
我试图更改我的所有者字段及其与 UserModel 的关系:
class Card(tmodels.Model):
owner = fields.ManyToManyField("models.UserModel", related_name="cards")
class UserModel(TortoiseBaseUserModel):
cards: fields.ManyToManyRelation["Card"]
不知道对不对,我弄不出来,可能是其他地方的问题。
我的端点为用户创建了一个项目:
@router.post("/", response_model=Card_Pydantic)
async def create_item_for_current_user(
card: CardCreate, user: UserDB = Depends(fastapi_users.get_current_active_user)):
card_obj = await crud.create_user_card(card=card, user=user)
if card_obj is None:
raise HTTPException(status_code=404, detail=f"City '{card.city_name}' not found")
return await card_obj
此处使用(创建项目)的 CRUD 函数:
async def create_user_card(
card: schemas.CardCreate, user: UserDB = Depends(fastapi_users.get_current_active_user)):
card_json = await weather_api.get_api_info_by_name(card.city_name)
if card_json is None:
return None
card_obj = await Card.create(**card_json,
owner_id=user.id) # ** is used to pass the keys values as kwargs.
return await card_obj
我获取用户拥有的项目的端点:
@router.get("/me/onlyitems/", response_model=List[schemas.Card_Pydantic])
async def read_current_user_items(user: UserDB = Depends(fastapi_users.get_current_active_user)):
user_cards = await schemas.Card_Pydantic.from_queryset(Card.filter(owner_id=user.id).all())
return await crud.update_only_card_for(user_cards, user.id)
我的 crud 函数是:
async def update_only_card_for(user_cards, owner_id):
for city in user_cards:
await update_card(city.id, owner_id)
return await schemas.Card_Pydantic.from_queryset(Card.filter(owner_id=owner_id).all())
# The update function (it will check if it need to update or not)
async def update_card(city_id: int, owner_id: UUID = UserDB):
card_obj = await Card.get(id=city_id)
card_time = card_obj.updated_at
timezone = card_time.tzinfo
now = datetime.now(timezone)
time_to_update = now - timedelta(minutes=15)
if card_time < time_to_update:
card_json = await weather_api.get_api_info_by_id(city_id)
await card_obj.update_from_dict(card_json).save() # with save will update the update_at field
card_updated = await schemas.Card_Pydantic.from_queryset_single(Card.get(id=city_id))
return card_updated
我如何更改它以获得用户需要访问的卡?我想我不能使用 owner_id 的过滤器,(好吧,如果我可以创建我的中介 table,我可以,但不知道怎么做和获取数据)但是可以'不考虑如何将 link 用户与他添加的项目联系起来,同时其他用户访问相同的项目。
此代码工作正常(我使用的是 nuxt.js 前端),直到出现重复的城市...
最好的处理方法是什么?如何处理?
谢谢,迭戈。
我解决了。我认为 Tortoise 文档有点混乱,但我找到了如何创建关系和检索数据。
车型:(简化版)
class Card(tmodels.Model):
id = fields.IntField(pk=True)
city_name = fields.CharField(max_length=30)
owners: fields.ManyToManyRelation["UserModel"] = fields.ManyToManyField(
"models.UserModel", related_name="cards", through="card_usermodel")
class UserModel(TortoiseBaseUserModel):
cards: fields.ManyToManyRelation[Card]
through="card_usermodel"
将使用卡 ID 和所有者 ID 创建一个中介 table,它将定义关系。
进行数据操作:
async def create_user_card(
card: schemas.CardCreate, user: UserDB = Depends(fastapi_users.get_current_active_user)):
card_json = await weather_api.get_api_info_by_name(card.city_name)
if card_json is None:
return None
owner = await UserModel.get_or_none(id=user.id)
card_obj = await Card.update_or_create(**card_json)
await card_obj[0].owners.add(owner)
return await schemas.Card_Response_Pydantic.from_queryset_single(Card.get(id=card_json.get("id"), owners=user.id))
基本就是这样。我也更改了一些 Pydantic 模型,以验证响应。
我正在使用 FastAPI、Tortoise ORM 和 FastAPI 用户制作一个 API 来学习。基本上我从 OpenWeather API 获取城市天气数据并使用 Tortoise ORM 存储在 SQLite 数据库中。我有 FastAPI 用户的身份验证。
我使用的是外键,关系运行良好。但现在我想要一些改变(改进)。我希望每个用户都有自己的物品,在登录并访问端点 (@router.get("/me/onlyitems/", response_model=List[schemas.Card_Pydantic])
) 后,它只收到他们的物品。
工作正常,我只收到用户项目(使用 owner_id),但我的项目 table 使用外部唯一 ID(项目是一个城市,每个城市都有一个唯一的ID),它是主键。 但是,当另一个用户尝试添加同一个城市时,它会给出一个错误,因为它不能在 table 中拥有同一个城市,即使是另一个所有者。好吧,应该是这样……但我想让其他用户访问同一个城市(他们有相同的信息,他们没有定制)。 然后,我希望许多用户可以访问同一项目(并且每个用户可以访问多个项目)。
我不认为为每个用户重复项目信息(使用另一个 ID)是理想的,不同的用户访问相同的项目会更好。
我认为这将是一个多对多关系,但我不知道如何建立关系并使用 FastAPI 获得它。
我想过创建一个中介table,只有UserID和CityID(和一个自增ID),然后用它来获取相关数据,但我不知道这是不是最好的方法或者有可能。
我试图从 Tortoise 文档中学习,但我找不到我需要的部分或理解一些复杂的示例和部分文档。
我正在使用软件包的更新版本和 Python 3.9.4
好吧,让我展示一些代码:
models.py
class UserModel(TortoiseBaseUserModel): # From FastAPI Users
cards: fields.ReverseRelation["Card"]
class OAuthAccountModel(TortoiseBaseOAuthAccountModel): # From FastAPI Users
user = fields.ForeignKeyField("models.UserModel", related_name="oauth_accounts")
class User(models.BaseUser, models.BaseOAuthAccountMixin): # From FastAPI Users
pass
class UserCreate(models.BaseUserCreate): # From FastAPI Users
pass
class UserUpdate(User, models.BaseUserUpdate): # From FastAPI Users
pass
class UserDB(User, models.BaseUserDB, PydanticModel): # From FastAPI Users
class Config:
orm_mode = True
orig_model = UserModel
user_db = TortoiseUserDatabase(UserDB, UserModel, OAuthAccountModel) # From FastAPI Users
class Card(tmodels.Model):
"""
The Card model, related to the user.
"""
id = fields.IntField(pk=True)
city_name = fields.CharField(max_length=30)
temperature = fields.FloatField()
state = fields.CharField(max_length=30, null=True)
icon = fields.CharField(max_length=3, null=True)
period = fields.CharField(max_length=20, null=True)
created_at = fields.DatetimeField(auto_now_add=True)
updated_at = fields.DatetimeField(auto_now=True)
owner: fields.ForeignKeyRelation[UserModel] = fields.ForeignKeyField(
"models.UserModel", related_name="cards")
class PydanticMeta:
exclude = ["owner"]
我试图更改我的所有者字段及其与 UserModel 的关系:
class Card(tmodels.Model):
owner = fields.ManyToManyField("models.UserModel", related_name="cards")
class UserModel(TortoiseBaseUserModel):
cards: fields.ManyToManyRelation["Card"]
不知道对不对,我弄不出来,可能是其他地方的问题。
我的端点为用户创建了一个项目:
@router.post("/", response_model=Card_Pydantic)
async def create_item_for_current_user(
card: CardCreate, user: UserDB = Depends(fastapi_users.get_current_active_user)):
card_obj = await crud.create_user_card(card=card, user=user)
if card_obj is None:
raise HTTPException(status_code=404, detail=f"City '{card.city_name}' not found")
return await card_obj
此处使用(创建项目)的 CRUD 函数:
async def create_user_card(
card: schemas.CardCreate, user: UserDB = Depends(fastapi_users.get_current_active_user)):
card_json = await weather_api.get_api_info_by_name(card.city_name)
if card_json is None:
return None
card_obj = await Card.create(**card_json,
owner_id=user.id) # ** is used to pass the keys values as kwargs.
return await card_obj
我获取用户拥有的项目的端点:
@router.get("/me/onlyitems/", response_model=List[schemas.Card_Pydantic])
async def read_current_user_items(user: UserDB = Depends(fastapi_users.get_current_active_user)):
user_cards = await schemas.Card_Pydantic.from_queryset(Card.filter(owner_id=user.id).all())
return await crud.update_only_card_for(user_cards, user.id)
我的 crud 函数是:
async def update_only_card_for(user_cards, owner_id):
for city in user_cards:
await update_card(city.id, owner_id)
return await schemas.Card_Pydantic.from_queryset(Card.filter(owner_id=owner_id).all())
# The update function (it will check if it need to update or not)
async def update_card(city_id: int, owner_id: UUID = UserDB):
card_obj = await Card.get(id=city_id)
card_time = card_obj.updated_at
timezone = card_time.tzinfo
now = datetime.now(timezone)
time_to_update = now - timedelta(minutes=15)
if card_time < time_to_update:
card_json = await weather_api.get_api_info_by_id(city_id)
await card_obj.update_from_dict(card_json).save() # with save will update the update_at field
card_updated = await schemas.Card_Pydantic.from_queryset_single(Card.get(id=city_id))
return card_updated
我如何更改它以获得用户需要访问的卡?我想我不能使用 owner_id 的过滤器,(好吧,如果我可以创建我的中介 table,我可以,但不知道怎么做和获取数据)但是可以'不考虑如何将 link 用户与他添加的项目联系起来,同时其他用户访问相同的项目。
此代码工作正常(我使用的是 nuxt.js 前端),直到出现重复的城市...
最好的处理方法是什么?如何处理?
谢谢,迭戈。
我解决了。我认为 Tortoise 文档有点混乱,但我找到了如何创建关系和检索数据。
车型:(简化版)
class Card(tmodels.Model):
id = fields.IntField(pk=True)
city_name = fields.CharField(max_length=30)
owners: fields.ManyToManyRelation["UserModel"] = fields.ManyToManyField(
"models.UserModel", related_name="cards", through="card_usermodel")
class UserModel(TortoiseBaseUserModel):
cards: fields.ManyToManyRelation[Card]
through="card_usermodel"
将使用卡 ID 和所有者 ID 创建一个中介 table,它将定义关系。
进行数据操作:
async def create_user_card(
card: schemas.CardCreate, user: UserDB = Depends(fastapi_users.get_current_active_user)):
card_json = await weather_api.get_api_info_by_name(card.city_name)
if card_json is None:
return None
owner = await UserModel.get_or_none(id=user.id)
card_obj = await Card.update_or_create(**card_json)
await card_obj[0].owners.add(owner)
return await schemas.Card_Response_Pydantic.from_queryset_single(Card.get(id=card_json.get("id"), owners=user.id))
基本就是这样。我也更改了一些 Pydantic 模型,以验证响应。