Skip to content

Add English localization for all files #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/apps/blog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@


def setup(app: FastAPI):
# 1. 导入管理应用
# 2. 注册普通路由
# 1. Import the admin application
# 2. Register normal routes
from . import admin, apis, events

app.include_router(apis.router)
63 changes: 28 additions & 35 deletions backend/apps/blog/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

@site.register_admin
class BlogApp(admin.AdminApp):
page_schema = PageSchema(label="博客应用", icon="fa fa-wordpress")
page_schema = PageSchema(label="Blog Application", icon="fa fa-wordpress")

def __init__(self, app: "AdminApp"):
super().__init__(app)
Expand All @@ -36,75 +36,69 @@ def __init__(self, app: "AdminApp"):


class CategoryAdmin(admin.ModelAdmin):
page_schema = PageSchema(label="分类管理", icon="fa fa-folder")
page_schema = PageSchema(label="Category Management", icon="fa fa-folder")
model = Category
search_fields = [Category.name]


class TagAdmin(admin.ModelAdmin):
page_schema = PageSchema(label="标签管理", icon="fa fa-tags")
page_schema = PageSchema(label="Tag Management", icon="fa fa-tags")
model = Tag
search_fields = [Tag.name]
link_model_fields = [Tag.articles]


class UserGender(IntegerChoices):
unknown = 0, "保密"
man = 1, ""
woman = 2, ""
unknown = 0, "Unknown"
man = 1, "Male"
woman = 2, "Female"


class TestAction(admin.ModelAction):
action = ActionType.Dialog(tooltip="自定义表单动作",icon="fa fa-star",level=LevelEnum.warning, dialog=Dialog())
action = ActionType.Dialog(tooltip="Custom Form Action",icon="fa fa-star",level=LevelEnum.warning, dialog=Dialog())

# 创建表单数据模型
class schema(BaseModel):
username: str = Field(..., title="用户名")
password: str = Field(..., title="密码", amis_form_item="input-password")
birthday: datetime.datetime = Field(None, title="出生日期")
gender: UserGender = Field(UserGender.unknown, title="性别")
is_active: bool = Field(True, title="是否激活")
username: str = Field(..., title="Username")
password: str = Field(..., title="Password", amis_form_item="input-password")
birthday: datetime.datetime = Field(None, title="Birth Date")
gender: UserGender = Field(UserGender.unknown, title="Gender")
is_active: bool = Field(True, title="Is Active")

async def handle(self, request: Request, item_id: List[str], data: schema, **kwargs) -> BaseApiOut[Any]:
items = await self.admin.fetch_items(*item_id)
return BaseApiOut(data={"item_id": item_id, "data": data, "items": list(items)})


class ArticleAdmin(admin.ModelAdmin):
page_schema = PageSchema(label="文章管理", icon="fa fa-file")
page_schema = PageSchema(label="Article Management", icon="fa fa-file")
model = Article
# 配置列表展示字段
list_display = [
Article.id,
Article.title,
Article.img,
Article.status,
Category.name,
TableColumn(type="tpl", label="自定义模板列", tpl='<a href="${source}" target="_blank">ID:${id},Title:${title}</a>'),
TableColumn(type="tpl", label="Custom Template Column", tpl='<a href="${source}" target="_blank">ID:${id},Title:${title}</a>'),
Article.create_time,
Article.description,
Category.name.label("category"), # 重命名字段;也可以使用sqlalchemy函数, 例如:
# func.count('*').label('article_count'), 注意在`get_select`中修改对应的sql查询语句
Category.name.label("category"),
LabelField(
Category.name.label("category2"),
Field("默认分类", title="分类名称"), # 通过Field配置Amis表格列信息,Amis表单字段信息.
Field("Default Category", title="Category Name"),
),
]
# 配置模糊搜索字段
search_fields = [Article.title, Category.name]
# 配置关联模型
link_model_fields = [Article.tags]
# 配置自定义动作
admin_action_maker = [
lambda self: TestAction(self, name="test_action", label="自定义动作", flags=["item", "bulk"]),
lambda self: TestAction(self, name="test_action", label="Custom Action", flags=["item", "bulk"]),
lambda self: AdminAction(
self,
name="iframe_action",
flags=["item"],
action=ActionType.Dialog(
icon="fa fa-star",
level=LevelEnum.warning,
tooltip="自定义Iframe动作",
tooltip="Custom Iframe Action",
dialog=Dialog(
size="lg",
body=amis.Iframe(
Expand All @@ -113,7 +107,7 @@ class ArticleAdmin(admin.ModelAdmin):
events={
"detail": {
"actionType": "dialog",
"dialog": {"title": "弹框", "body": "iframe 传给 amis id :${iframeId}"},
"dialog": {"title": "Dialog", "body": "iframe passed to amis id is:${iframeId}"},
}
},
),
Expand All @@ -126,7 +120,7 @@ class ArticleAdmin(admin.ModelAdmin):
name="toolbar_action1",
flags=["toolbar"],
action=ActionType.Ajax(
label="工具条ajax动作",
label="Toolbar Ajax Action",
level=LevelEnum.danger,
api="https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/form/saveForm",
),
Expand All @@ -136,45 +130,44 @@ class ArticleAdmin(admin.ModelAdmin):
name="toolbar_action2",
flags=["toolbar"],
action=ActionType.Link(
label="工具条link动作", level=LevelEnum.secondary, link="https://github.com/amisadmin/fastapi_amis_admin"
label="Toolbar Link Action", level=LevelEnum.secondary, link="https://github.com/amisadmin/fastapi_amis_admin"
),
),
lambda self: AdminAction(
self,
name="toolbar_action3",
flags=["toolbar"],
action=ActionType.Drawer(
label="工具条抽屉动作",
label="Toolbar Drawer Action",
level=LevelEnum.info,
drawer={
"title": "表单设置",
"title": "Form Settings",
"body": {
"type": "form",
"api": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/form/saveForm?waitSeconds"
"=1",
"body": [
{"type": "input-text", "name": "text", "label": "文本"},
{"type": "input-text", "name": "text", "label": "Text"},
{
"type": "input-number",
"name": "number",
"label": "数字",
"label": "Number",
"placeholder": "",
"inline": True,
"value": 5,
"min": 1,
"max": 10,
},
{"type": "input-rating", "count": 5, "value": 3, "label": "评分", "name": "rating"},
{"type": "input-datetime", "name": "datetime", "inline": True, "label": "日期+时间"},
{"type": "input-rating", "count": 5, "value": 3, "label": "Rating", "name": "rating"},
{"type": "input-datetime", "name": "datetime", "inline": True, "label": "Date + Time"},
],
},
},
),
),
]
display_item_action_as_column = True # 将item_action显示为列
display_item_action_as_column = True

# 自定义查询选择器
async def get_select(self, request: Request) -> Select:
sel = await super().get_select(request)
return sel.outerjoin(Category)
51 changes: 15 additions & 36 deletions backend/apps/blog/apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,30 @@

router = APIRouter(prefix="/articles", tags=["ArticleAPI"])


# 方式一: 通过FastAPI依赖自动处理,获取session.(推荐) 特点:
# 1.同一个请求FastAPI会缓存依赖,对于多级依赖,同一个请求多次使用session对象,会减少session的获取成本.
# 2.如果`site.db`使用的是`AsyncDatabase`异步连接,则获取的是`AsyncSession`.
# 3.如果`site.db`使用的是`Database`同步连接,则获取的是`Session`.
# 2.1.同步Session可以充分利用`sqlalchemy`模型懒加载的特性.
# 2.2.注意不要在异步方法中使用同步Session,否则可能堵塞异步循环.
@router.get("/read/{id}", response_model=Article, summary="读取文章")
# Way 1: Use FastAPI dependencies to handle sessions automatically. (Recommended)
# Note: FastAPI will cache dependencies for the same request.
# Be cautious when using synchronous sessions in asynchronous methods.
@router.get("/read/{id}", response_model=Article, summary="Read article")
async def read_article(id: int, session: AsyncSess):
return await session.get(Article, id)


@router.get("/update/{id}", response_model=Article, summary="更新文章")
@router.get("/update/{id}", response_model=Article, summary="Update article")
async def update_article(id: int, session: AsyncSess):
article = await session.get(Article, id)
if article:
article.create_time = datetime.datetime.now()
await session.flush()
return article


# 方式二: 通过`sqlalchemy-database`提供的快捷函数.特点:
# 1.会自动获取当前上下文的session.(请理解session的获取原理,否则不要使用此方式)
# 1.1.如果需要多次使用同一个session,可以通过封装一个函数, 使用`run_sync`方法,减少session获取成本.
# 2.通过`async_`前缀方法,可以不用区分数据库连接是同步连接,还是异步连接.
# 2.1对于特定的项目,在能确定数据库连接是同步或异步的情况下,建议明确调用对应的方法.
# 2.2如果开发一个python包,供其他人使用不能确定连接是同步或异步,应该统一使用`async_`前缀方法.


@router.get("/read2/{id}", response_model=Article, summary="读取文章")
# Way 2: Use the shortcut functions provided by `sqlalchemy-database`.
@router.get("/read2/{id}", response_model=Article, summary="Read article")
async def read_article2(id: int):
article = await site.db.async_get(Article, id)
return article


@router.put("/update2/{id}", response_model=Article, summary="更新文章")
@router.put("/update2/{id}", response_model=Article, summary="Update article")
async def update_article2(id: int):
article = await site.db.async_get(Article, id)
if article:
Expand All @@ -54,34 +42,27 @@ async def update_article2(id: int):
return article


@router.post("/create2/", response_model=int, summary="新增文章")
@router.post("/create2/", response_model=int, summary="Create article")
async def create_article2(data: Article):
# 新增数据模型根据实际情况自己定义
stmt = insert(Article).values(data.dict(exclude={"id"}))
result = await site.db.async_execute(stmt)
return result.lastrowid


@router.get("/list2", response_model=List[Article], summary="读取文章列表")
@router.get("/list2", response_model=List[Article], summary="List articles")
async def list_article2():
# 通用的查询表达式可以写在ORM模型,提供一个方法调用.
stmt = select(Article).where(Article.status == ArticleStatus.published.value).limit(10).order_by(Article.create_time)
result = await site.db.async_scalars(stmt)
return result.all()


# 方式三: 通过注册中间件,在每次请求时自动获取session,并在请求结束时自动关闭session.
# 注意: 必须在fastapi应用中注册中间件,才能在请求中获取session.(请理解session的获取原理,否则不要使用此方式)
# 1.如果`site.db`使用的是`AsyncDatabase`异步连接,则获取的是`AsyncSession`.
# 2.如果`site.db`使用的是`Database`同步连接,则获取的是`Session`.
# 建议在`core.globals.py`中建立一个`db`对象,用于管理数据库连接.
@router.get("/read3/{id}", response_model=Article, summary="读取文章")
# Way 3: Register a middleware to handle session automatically on each request.
@router.get("/read3/{id}", response_model=Article, summary="Read article")
async def read_article3(id: int):
article = await site.db.session.get(Article, id)
return article


@router.put("/update3/{id}", response_model=Article, summary="更新文章")
@router.put("/update3/{id}", response_model=Article, summary="Update article")
async def update_article3(id: int):
article = await site.db.session.get(Article, id)
if article:
Expand All @@ -90,17 +71,15 @@ async def update_article3(id: int):
return article


@router.post("/create3/", response_model=int, summary="新增文章")
@router.post("/create3/", response_model=int, summary="Create article")
async def create_article3(data: Article):
# 新增数据模型根据实际情况自己定义
stmt = insert(Article).values(data.dict(exclude={"id"}))
result = await site.db.session.execute(stmt)
return result.lastrowid


@router.get("/list3", response_model=List[Article], summary="读取文章列表")
@router.get("/list3", response_model=List[Article], summary="List articles")
async def list_article3():
# 通用的查询表达式可以写在ORM模型,提供一个方法调用.
stmt = select(Article).where(Article.status == ArticleStatus.published.value).limit(10).order_by(Article.create_time)
result = await site.db.session.scalars(stmt)
return result.all()
6 changes: 4 additions & 2 deletions backend/apps/blog/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

@event.listens_for(Article, "before_insert")
def receive_before_insert(mapper, connection: Connection, article: Article):
"""监听创建文章"""
"""Listen for the creation of an article"""
# sess = object_session(article)
# do something


@event.listens_for(Article.status, "set")
def receive_set(article: Article, value, old, initiator):
"""监听文章状态改变"""
"""Listen for changes to the article's status"""
# sess = object_session(article)
# do something


36 changes: 21 additions & 15 deletions backend/apps/blog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,52 @@


class ArticleStatus(IntegerChoices):
unpublished = 0, "未发布"
published = 1, "已发布"
inspection = 2, "审核中"
disabled = 3, "已禁用"
# Article status enum
unpublished = 0, "Unpublished"
published = 1, "Published"
inspection = 2, "Under Review"
disabled = 3, "Disabled"


class Category(SQLModel, table=True):
# Article category
id: int = Field(default=None, primary_key=True, nullable=False)
name: str = Field(title="CategoryName", sa_column=Column(String(100), unique=True, index=True, nullable=False))
name: str = Field(title="Category Name", sa_column=Column(String(100), unique=True, index=True, nullable=False))
description: str = Field(default="", title="Description", amis_form_item="textarea")
status: bool = Field(None, title="status")
status: bool = Field(None, title="Status")
articles: List["Article"] = Relationship(back_populates="category")


class ArticleTagLink(SQLModel, table=True):
# Link table between articles and tags
tag_id: Optional[int] = Field(default=None, foreign_key="tag.id", primary_key=True)
article_id: Optional[int] = Field(default=None, foreign_key="article.id", primary_key=True)


class Tag(SQLModel, table=True):
# Article tags
id: int = Field(default=None, primary_key=True, nullable=False)
name: str = Field(..., title="TagName", sa_column=Column(String(255), unique=True, index=True, nullable=False))
name: str = Field(..., title="Tag Name", sa_column=Column(String(255), unique=True, index=True, nullable=False))
articles: List["Article"] = Relationship(back_populates="tags", link_model=ArticleTagLink)


class Article(SQLModel, table=True):
# Article model
id: int = Field(default=None, primary_key=True, nullable=False)
title: str = Field(title="ArticleTitle", max_length=200)
title: str = Field(title="Article Title", max_length=200)
img: str = Field(
None,
title="ArticleImage",
title="Article Image",
max_length=300,
amis_form_item=InputImage(maxLength=1, maxSize=2 * 1024 * 1024, receiver="post:/admin/file/upload"),
amis_table_column=ColumnImage(width=100, height=60, enlargeAble=True),
)
description: str = Field(default="", title="ArticleDescription", amis_form_item="textarea")
status: ArticleStatus = Field(ArticleStatus.unpublished, title="status")
content: str = Field(..., title="ArticleContent", amis_form_item=InputRichText())
create_time: Optional[datetime] = Field(default_factory=datetime.utcnow, title="CreateTime")
category_id: Optional[int] = Field(default=None, foreign_key="category.id", title="CategoryId")
description: str = Field(default="", title="Article Description", amis_form_item="textarea")
status: ArticleStatus = Field(ArticleStatus.unpublished, title="Status")
content: str = Field(..., title="Article Content", amis_form_item=InputRichText())
create_time: Optional[datetime] = Field(default_factory=datetime.utcnow, title="Create Time")
category_id: Optional[int] = Field(default=None, foreign_key="category.id", title="Category ID")
category: Optional[Category] = Relationship(back_populates="articles")
tags: List[Tag] = Relationship(back_populates="articles", link_model=ArticleTagLink)
source: str = Field(default="", title="ArticleSource", max_length=200)
source: str = Field(default="", title="Article Source", max_length=200)

2 changes: 1 addition & 1 deletion backend/apps/demo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@


def setup(app: FastAPI):
# 1. 导入管理应用
# 1. Import the admin application
from . import admin
Loading