add summary
test / pytest (push) Successful in 40s
docker-image / build-and-push (push) Successful in 4m17s

This commit is contained in:
2026-04-27 20:43:57 +02:00
parent d24c41d05f
commit facf82c898
4 changed files with 113 additions and 1 deletions
+31 -1
View File
@@ -88,6 +88,35 @@ def _wants_add_next(submit_action: str | None) -> bool:
return submit_action == "save_and_add_next" return submit_action == "save_and_add_next"
def _format_average(total: int, divisor: int) -> str:
if divisor == 0:
return "0.0"
return f"{total / divisor:.1f}"
def _build_boxes_overview_summary(db: Session) -> dict[str, int | str]:
box_count = db.query(func.count(Box.id)).scalar() or 0
item_count = db.query(func.count(Item.id)).scalar() or 0
subitem_count = db.query(func.count(SubItem.id)).scalar() or 0
container_item_count = (
db.query(func.count(Item.id))
.filter(Item.is_container.is_(True))
.scalar()
or 0
)
return {
"box_count": box_count,
"item_count": item_count,
"item_and_subitem_count": item_count + subitem_count,
"avg_items_per_box": _format_average(item_count, box_count),
"avg_subitems_per_container_item": _format_average(
subitem_count,
container_item_count,
),
}
def _build_search_results(db: Session, query: str) -> list[dict]: def _build_search_results(db: Session, query: str) -> list[dict]:
keyword = f"%{query.lower()}%" keyword = f"%{query.lower()}%"
results: list[dict] = [] results: list[dict] = []
@@ -231,10 +260,11 @@ def create_app() -> FastAPI:
@app.get("/boxes") @app.get("/boxes")
def list_boxes(request: Request, db: Session = Depends(get_db)): def list_boxes(request: Request, db: Session = Depends(get_db)):
boxes = db.query(Box).order_by(Box.id.desc()).all() boxes = db.query(Box).order_by(Box.id.desc()).all()
summary = _build_boxes_overview_summary(db)
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="boxes/index.html", name="boxes/index.html",
context={"page_title": "箱子", "boxes": boxes}, context={"page_title": "箱子", "boxes": boxes, "summary": summary},
) )
@app.get("/boxes/new") @app.get("/boxes/new")
+19
View File
@@ -231,6 +231,25 @@ button:focus-visible {
gap: 10px; gap: 10px;
} }
.summary-section {
margin-bottom: 18px;
}
.summary-block {
padding-top: 12px;
padding-bottom: 12px;
}
.summary-list {
margin: 0;
padding-left: 18px;
color: #333;
}
.summary-list li + li {
margin-top: 6px;
}
.compact-row { .compact-row {
display: grid; display: grid;
grid-template-columns: auto 1fr auto; grid-template-columns: auto 1fr auto;
+16
View File
@@ -15,6 +15,22 @@
<a class="button button-primary" href="/boxes/new">新建箱子</a> <a class="button button-primary" href="/boxes/new">新建箱子</a>
</div> </div>
<section class="stack summary-section">
<div class="section-heading">
<h2>当前概览</h2>
<p class="muted">快速查看当前装箱记录的核心统计。</p>
</div>
<section class="card summary-block">
<ul class="summary-list">
<li><strong>箱子总数:</strong>{{ summary.box_count }}</li>
<li><strong>物品总数(不含子物品):</strong>{{ summary.item_count }}</li>
<li><strong>物品总数(含子物品):</strong>{{ summary.item_and_subitem_count }}</li>
<li><strong>平均每箱物品数:</strong>{{ summary.avg_items_per_box }}</li>
<li><strong>平均每个容器型 Item 的子物品数:</strong>{{ summary.avg_subitems_per_container_item }}</li>
</ul>
</section>
</section>
{% if boxes %} {% if boxes %}
<div class="overview-grid"> <div class="overview-grid">
{% for box in boxes %} {% for box in boxes %}
+47
View File
@@ -178,6 +178,53 @@ def test_boxes_overview_renders_cleanly_when_note_is_empty(client, db_session):
assert "状态:" not in response.text assert "状态:" not in response.text
def test_boxes_overview_summary_shows_expected_counts_and_averages(client, db_session):
first_box = Box(name="卧室箱")
second_box = Box(name="书房箱")
regular_item = Item(name="", box=first_box, is_container=False)
container_item = Item(name="配件袋", box=first_box, is_container=True)
second_container_item = Item(name="文件袋", box=second_box, is_container=True)
db_session.add_all(
[
first_box,
second_box,
regular_item,
container_item,
second_container_item,
SubItem(name="转接头", parent_item=container_item),
SubItem(name="数据线", parent_item=container_item),
]
)
db_session.commit()
response = client.get("/boxes")
assert response.status_code == 200
assert "当前概览" in response.text
assert "箱子总数" in response.text
assert "物品总数(不含子物品)" in response.text
assert "物品总数(含子物品)" in response.text
assert "平均每箱物品数" in response.text
assert "平均每个容器型 Item 的子物品数" in response.text
assert "箱子总数:</strong>2" in response.text
assert "物品总数(不含子物品):</strong>3" in response.text
assert "物品总数(含子物品):</strong>5" in response.text
assert "平均每箱物品数:</strong>1.5" in response.text
assert "平均每个容器型 Item 的子物品数:</strong>1.0" in response.text
def test_boxes_overview_summary_handles_empty_data_safely(client):
response = client.get("/boxes")
assert response.status_code == 200
assert "当前概览" in response.text
assert "箱子总数:</strong>0" in response.text
assert "物品总数(不含子物品):</strong>0" in response.text
assert "物品总数(含子物品):</strong>0" in response.text
assert "平均每箱物品数:</strong>0.0" in response.text
assert "平均每个容器型 Item 的子物品数:</strong>0.0" in response.text
def test_can_create_box(client, db_session): def test_can_create_box(client, db_session):
response = create_box(client, name="Kitchen Box") response = create_box(client, name="Kitchen Box")