微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

FastAPI + Tortoise ORM + FastAPI 用户 (Python) - 关系 - 多对多

如何解决FastAPI + Tortoise ORM + FastAPI 用户 (Python) - 关系 - 多对多

我正在使用 FastAPI、Tortoise ORM 和 FastAPI 用户来制作 API 以供学习。基本上我从 OpenWeather API 获取城市天气数据并使用 Tortoise ORM 存储在 sqlite 数据库中。我已通过 FastAPI 用户进行身份验证。

我使用的是外键并且关系运行良好。但现在我想要一些改变(改进)。我希望每个用户都有他们的项目,并且在登录并访问端点 (@router.get("/me/onlyitems/",response_model=List[schemas.Card_Pydantic])) 后,它只接收他们的项目。

它工作正常,我只收到用户项目(使用 owner_id),但我的项目表使用外部唯一 ID(项目是一个城市,每个城市都有一个唯一 ID)并且它是主键。 但是,当另一个用户尝试添加一个城市时,它会出错,因为它不能在表中包含同一个城市,即使是另一个所有者。嗯,应该是这样......但我想让其他用户访问相同的城市(他们有相同的信息,他们没有定制)。 然后,我希望许多用户可以访问同一个项目(并且每个用户可以访问多个项目)。

我不认为为每个用户重复项目信息(使用另一个 ID)是理想的,不同的用户访问同一项目会更好。

我认为这将是一个多对多的关系,但我不知道如何建立这种关系并通过 FastAPI 获得它。

我想过创建一个中间表,只有 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 使用过滤器,(好吧,如果我可以创建我的中间表,我可以,但不知道如何做并获取数据)但无法考虑如何与将用户链接到他添加的项目,同时其他用户访问相同的项目。

代码工作正常(我使用的是 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" 将创建一个具有 Card id 和所有者 id 的中间表,它将定义关系。

进行数据操作:

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 模型,以验证响应。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。