fastapi项目 06-新增项目接口,序列化查询结果

前言

新增项目,一般都会记录是哪个用户新建的,往往一个用户可以新建多个项目,于是数据库模型就可以建立一对多的关系。

1. 新建数据库模型(一对多)

# apps/model/models.py  

class User(DbModel)
    ......
    # 一对多
    projects = relationship('Project', backref='user', cascade='all,delete')


class Project(DbModel):
    """项目信息"""
    __tablename__ = 'project'
    id = Column(Integer, primary_key=True, autoincrement=True)
    project_name = Column(String(100), unique=True, nullable=False)
    publish_app = Column(String(100), nullable=True)
    project_des = Column(String(600), nullable=True)
    active = Column(Enum("1", "0"), default="1", nullable=True)
    # 所属用户
    user_id = Column(Integer, ForeignKey('user.id'))

新增数据库模型后,就可以进行表的迁移:

  • alembic revision --autogenerate -m "test" # 自定义迁移文件名
  • alembic upgrade head
    迁移成功后,就可以在数据库中查看到新建了数据库表project。

1.1 新增crud

# apps/routers/crud.py  
from apps.model.models import User, Project

def create_project(db: Session, body):
    db_project = Project(**body.dict())
    # 先判断是否存在,在入库
    if db.query(Project).filter_by(project_name=db_project.project_name).count():
        # 项目名称重复添加
        raise HTTPException(status_code=400, detail=f'项目名称:{body.project_name},已存在')
    try:
        db.add(db_project)
        db.commit()
        db.refresh(db_project)
    except Exception as e:
        print(f"数据写入失败:{e}")
        raise HTTPException(status_code=400, detail='添加项目失败!')
    return db_project

创建新建项目的接口函数,并约定数据输入和输出模型。
我们先建立数据的输入和输出模型。

# apps/model/schemas.py
class ProjectIn(BaseModel):
    project_name: str = Field(..., max_length=100, description="项目名称")
    publish_app: str = Field(None, max_length=100)
    project_des: str = Field(None, max_length=600)
    user_id: int = Field(None)

class ProjectOut(BaseModel):
    id: int
    project_name: str
    project_app: str = ''
    project_des: str = ''
    active: str = '1'
    user_id: int = ''

    class Config:
        orm_mode = True  # 将查询到的orm对象,转换成json
# apps/routers/view_project.py

from fastapi import APIRouter, Depends
from fastapi_jwt_auth import AuthJWT
from apps.dependency import get_db
from sqlalchemy.orm import Session
from apps.model.schemas import ProjectIn, ProjectOut
from apps.model.crud import create_project

router = APIRouter()

@router.post("/project", response_model=ProjectOut)
async def create_project_demo(
        body: ProjectIn,
        Authorize: AuthJWT = Depends(),
        db: Session = Depends(get_db)
):
    """新增项目数据"""
    Authorize.jwt_required()  # 需要先登录才能请求该接口
    # 获取额外的数据
    user_claims= Authorize.get_raw_jwt()
    # 得到用户名
    print(f'用户名:{user_claims["username"]}')
    body.user_id = user_claims["id"]
    res = create_project(db, body)

    return res

工厂函数create_app中,注册路由。

# 注册路由
app.include_router(view_project.router, prefix="/api/v1", tags=["项目"])

1.2 设置全局response model

正常在接口返回的内容一般都是以{"code": 0, "msg": "success", "data": data}的形式返回的。于是我们可以设置全局response model。

"""定义统一的返回格式"""
# apps/model/schema.py

from typing import Any, Optional, Dict
from starlette.background import BackgroundTask
from starlette.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
class FormatJSONResponse(JSONResponse):
    def __init__(self,
                 data: Any = None,
                 code: int = 0,
                 msg: str = 'success',
                 status_code: int = 200,
                 headers: Optional[Dict[str, str]] = None,
                 background: Optional[BackgroundTask] = None
                 ) -> None:
        content = jsonable_encoder({
            'code': code,
            'msg': msg,
            'data': data
        })
        super().__init__(content=content, status_code=status_code,
                         headers=headers, media_type='application/json', background=background)
from apps.model.schemas import FormatJSONResponse
def create_app():
    """工厂函数"""
    app = FastAPI(default_response_class=FormatJSONResponse)

重新运行后,发现返回的数据,已经加上了code和msg。

2.1 新增查询接口

查询项目的方式有多种,一种是传一个路径参数p_id,一种是传查询参数,查询多个或全部。


@router.get("/project/{p_id}", response_model=ProjectOut)
async def get_all_demo(
        p_id: int,
        Authorize: AuthJWT = Depends(),
        db: Session = Depends(get_db)
):
    Authorize.jwt_required()  # 需要先登录才能请求该接口
    user_claims = Authorize.get_raw_jwt()
    print(f'用户传进来的p_id: {p_id}')
    # 查询数据库
    res = query_project_by_id(db, p_id)
    return res

在crud中创建查询项目的函数。

# crud.py
def query_project_by_id(db: Session, p_id: int):
    query_project = db.query(Project).filter_by(id=p_id)
    if query_project.count() > 0:
        return query_project.first()
    else:
        raise HTTPException(status_code=404, detail="project 项目id未找到!")


继续我们设置谁创建的项目谁才有权限查询,admin账号我们让它都能查询,由于在数据库中未添加user的is_admin字段,于是我们在登录的时候,指定一个is_admin参数,传入。

def query_project_by_id(db: Session, p_id: int, user_claims):
    # 权限,每个人只能查询自己创建的,admin账号查询全部
    if user_claims.get('is_admin'):
        query_project = db.query(Project).filter_by(id=p_id)
    else:
        query_project = db.query(Project).filter_by(id=p_id, user_id=user_claims['id'])
    if query_project.count() > 0:
        return query_project.first()
    else:
        raise HTTPException(status_code=404, detail="project 项目id未找到!")

2.2 新增删除接口

我们直接通过id去删除项目。

@router.delete("/project/{p_id}")
async def del_project(
        p_id: int,
        Authorize: AuthJWT = Depends(),
        db: Session = Depends(get_db)
):
    Authorize.jwt_required()
    user_claims = Authorize.get_raw_jwt()
    print(f'用户传进来的p_id:{p_id}')
    # 执行数据库删除
    delete_project_by_id(db, p_id, user_claims)

在crud中,我们只允许admin账号才有删除的权限。

def delete_project_by_id(db: Session, p_id: int, user_claims):
    project =db.query(Project).filter_by(id=p_id)
    if not project.count() > 0:
        raise HTTPException(status_code=404, detail="project id not found!")
    if user_claims.get('is_admin'):
        db.query(Project).filter_by(id=p_id).delete()
        db.commit()
    else:
        raise HTTPException(status_code=403, detail="无权限操作,请联系管理员")

2.3 新增更新接口

话不多说,直接上代码。

class UpdateProjectIn(BaseModel):
    project_name: str = Field(default='', title='项目名称', description='项目名称', min_length=1, max_length=100)
    publish_app: str = Field(default='', title='发布应用', description='项目名称', max_length=100)
    project_des: str = Field(default='', title='描述', description='描述', max_length=100)
# crud.py

def update_project_by_id(db: Session, p_id: int, user_claims, body):
    project = db.query(Project).filter_by(id=p_id)
    if project.count() < 1:
        raise HTTPException(status_code=404, detail="project id not found!")
    if not user_claims.get('is_admin'):
        if project.first().user_id != user_claims.get('id'):
            raise HTTPException(status_code=403, detail="当前用户无权限操作,请联系管理员")
    # 修改
    project_name = body.project_name
    count = db.query(Project) \
        .filter(Project.id!=p_id) \
        .filter_by(project_name=project_name) \
        .count()
    if count > 0:  # 查询判断不能与数据库已有的项目名称重复
        raise HTTPException(status_code=403, detail=f"project name:{project_name},已存在")
    try:
        # exclude_unset=True 表示去掉未传值字段,避免未传值将原始的值更新为空
        project.update(body.dict(exclude_unset=True))
        db.commit()
    except Exception as e:
        print(f'----update error-------:{e}')
        raise HTTPException(status_code=409, detail='update error')

@router.put("/project/{p_id}")
async def put_project(
        p_id: int,
        body: UpdateProjectIn,
        Authorize: AuthJWT = Depends(),
        db: Session = Depends(get_db)
):
    Authorize.jwt_required()
    user_claims = Authorize.get_raw_jwt()
    print(f'用户传进来的p_id:{p_id}')
    # 执行数据库操作
    update_project_by_id(db, p_id, user_claims, body)
    return {'message': 'update success!'}

2.4 新增条件过滤查询

查询数据接口,一般都支持条件过滤,根据输入的条件,选择过滤的信息。

# crud.py
def query_all_projects(db: Session, user_claims, project_name=''):
    if user_claims.get('is_admin'):
        objs = db.query(Project)
    else:
        objs = db.query(Project).filter_by(user_id=user_claims.get('id'))

    return {"total": objs.count(), "data": objs.all()}
class AllProjectOut(BaseModel):
    total: int
    data: List[ProjectOut]
    class Config:
        orm_mode = True  # 将查询到的orm对象,转换成json
@router.get("/project", response_model=AllProjectOut)
async def get_all_projects(
        Authorize: AuthJWT = Depends(),
        db: Session = Depends(get_db)
):
    Authorize.jwt_required()
    user_claims = Authorize.get_raw_jwt()
    all_projects = query_all_projects(db, user_claims)
    return all_projects

这样即可实现查询全部的项目信息。后面我们增加条件过滤参数,根据条件查询出指定的项目信息。只需要在crud中增加project_name筛选即可。

# crud.py
def query_all_projects(db: Session, user_claims, project_name=''):
    if user_claims.get('is_admin'):
        objs = db.query(Project).order_by(desc(Project.id))
    else:
        objs = db.query(Project).filter_by(user_id=user_claims.get('id')).order_by(desc(Project.id))
    # 过滤条件查询
    objs = objs.filter(
        or_(Project.project_name.contains(project_name), project_name=='')
    )
    return {"total": objs.count(), "data": objs.all()}
@router.get("/project", response_model=AllProjectOut)
async def get_all_projects(
        project_name: str = Query(default='', example='项目'),  # 增加查询参数
        Authorize: AuthJWT = Depends(),
        db: Session = Depends(get_db)
):
    Authorize.jwt_required()
    user_claims = Authorize.get_raw_jwt()
    all_projects = query_all_projects(db, user_claims, project_name)
    return all_projects

2.5 增加分页查询

分页查询一般都是以page=1&size=10这种形式进行传递参数的,于是我们修改接口。

def paginate_by_page_size(objs, page=1, size=None):
    """分页查询"""
# crud.py
    if (page if page else 0) > 0 and (size if size else 0) > 0:
        offset_data = (page - 1) * (size if size else 0)
        objs = objs.offset(offset_data).limit(size)
    return objs
def query_all_projects(db: Session, user_claims, project_name='', page=1, size=None):
    if user_claims.get('is_admin'):
        objs = db.query(Project).order_by(desc(Project.id))
    else:
        objs = db.query(Project).filter_by(user_id=user_claims.get('id')).order_by(desc(Project.id))
    # 过滤条件查询
    objs = objs.filter(
        or_(Project.project_name.contains(project_name), project_name=='')
    )
    # 分页查询
    objs = paginate_by_page_size(objs=objs, page=page, size=size)
    return {"total": objs.count(), "data": objs.all()}
@router.get("/project", response_model=AllProjectOut)
async def get_all_projects(
        project_name: str = Query(default='', example='项目'),  # 增加查询参数
        page: int = Query(default=1, example='1'),
        size: int = Query(default=10, example='10'),
        Authorize: AuthJWT = Depends(),
        db: Session = Depends(get_db)
):
    Authorize.jwt_required()
    user_claims = Authorize.get_raw_jwt()
    all_projects = query_all_projects(db, user_claims, project_name, page, size)
    return all_projects

posted @ 2024-01-06 15:26  dack_deng  阅读(198)  评论(0编辑  收藏  举报