diff --git a/app/main.py b/app/main.py
index ce6cd0f..71dbca8 100644
--- a/app/main.py
+++ b/app/main.py
@@ -82,6 +82,10 @@ def _image_response_or_404(target) -> Response:
return Response(content=target.image_blob, media_type=target.image_mime_type)
+def _wants_add_next(submit_action: str | None) -> bool:
+ return submit_action == "save_and_add_next"
+
+
def _build_search_results(db: Session, query: str) -> list[dict]:
keyword = f"%{query.lower()}%"
results: list[dict] = []
@@ -333,6 +337,7 @@ def create_app() -> FastAPI:
note: str | None = Form(default=None),
quantity: str | None = Form(default=None),
is_container: str | None = Form(default=None),
+ submit_action: str | None = Form(default=None),
image_file: UploadFile | None = File(default=None),
db: Session = Depends(get_db),
) -> RedirectResponse:
@@ -348,7 +353,13 @@ def create_app() -> FastAPI:
db.add(item)
db.commit()
db.refresh(item)
- return RedirectResponse(url=f"/items/{item.id}", status_code=status.HTTP_303_SEE_OTHER)
+ if _wants_add_next(submit_action):
+ redirect_url = f"/boxes/{box.id}/items/new"
+ elif item.is_container:
+ redirect_url = f"/items/{item.id}"
+ else:
+ redirect_url = f"/boxes/{box.id}"
+ return RedirectResponse(url=redirect_url, status_code=status.HTTP_303_SEE_OTHER)
@app.get("/items/{item_id}")
def show_item(item_id: int, request: Request, db: Session = Depends(get_db)):
@@ -436,6 +447,7 @@ def create_app() -> FastAPI:
name: str = Form(...),
note: str | None = Form(default=None),
quantity: str | None = Form(default=None),
+ submit_action: str | None = Form(default=None),
image_file: UploadFile | None = File(default=None),
db: Session = Depends(get_db),
) -> RedirectResponse:
@@ -451,7 +463,11 @@ def create_app() -> FastAPI:
db.add(subitem)
db.commit()
db.refresh(subitem)
- return RedirectResponse(url=f"/items/{item.id}", status_code=status.HTTP_303_SEE_OTHER)
+ if _wants_add_next(submit_action):
+ redirect_url = f"/items/{item.id}/subitems/new"
+ else:
+ redirect_url = f"/items/{item.id}"
+ return RedirectResponse(url=redirect_url, status_code=status.HTTP_303_SEE_OTHER)
@app.get("/subitems/{subitem_id}/image")
def get_subitem_image(subitem_id: int, db: Session = Depends(get_db)) -> Response:
diff --git a/app/static/style.css b/app/static/style.css
index f25d25c..5fca068 100644
--- a/app/static/style.css
+++ b/app/static/style.css
@@ -63,6 +63,12 @@ button,
padding: 10px 14px;
}
+.button-secondary {
+ background: #eef3f8;
+ color: #1f2937;
+ border: 1px solid #cbd5e1;
+}
+
.button:hover,
button:hover {
opacity: 0.92;
@@ -280,6 +286,12 @@ button:hover {
gap: 14px;
}
+.form-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
.form-field {
margin: 0;
}
diff --git a/app/templates/items/form.html b/app/templates/items/form.html
index 33a686e..d25350e 100644
--- a/app/templates/items/form.html
+++ b/app/templates/items/form.html
@@ -77,6 +77,13 @@
{% endif %}
-
+
+
+ {% if not item %}
+
+ {% endif %}
+
{% endblock %}
diff --git a/app/templates/subitems/form.html b/app/templates/subitems/form.html
index 497e071..357af76 100644
--- a/app/templates/subitems/form.html
+++ b/app/templates/subitems/form.html
@@ -72,6 +72,13 @@
{% endif %}
-
+
+
+ {% if not subitem %}
+
+ {% endif %}
+
{% endblock %}
diff --git a/tests/test_app.py b/tests/test_app.py
index 4073e8d..3dfdc6d 100644
--- a/tests/test_app.py
+++ b/tests/test_app.py
@@ -21,19 +21,28 @@ def create_item(
quantity="2",
is_container=False,
image=None,
+ submit_action="save",
):
- data = {"name": name, "note": note, "quantity": quantity}
+ data = {"name": name, "note": note, "quantity": quantity, "submit_action": submit_action}
if is_container:
data["is_container"] = "on"
files = {"image_file": image} if image is not None else None
return client.post(f"/boxes/{box_id}/items", data=data, files=files, follow_redirects=False)
-def create_subitem(client, item_id, name="SubItem A", note="Small", quantity="3", image=None):
+def create_subitem(
+ client,
+ item_id,
+ name="SubItem A",
+ note="Small",
+ quantity="3",
+ image=None,
+ submit_action="save",
+):
files = {"image_file": image} if image is not None else None
return client.post(
f"/items/{item_id}/subitems",
- data={"name": name, "note": note, "quantity": quantity},
+ data={"name": name, "note": note, "quantity": quantity, "submit_action": submit_action},
files=files,
follow_redirects=False,
)
@@ -320,14 +329,13 @@ def test_post_redirects_are_reasonable(client, db_session):
db_session.commit()
item_response = create_item(client, box.id, name="Lamp")
- item_id = int(item_response.headers["location"].split("/")[-1])
- item = db_session.get(Item, item_id)
+ item = db_session.query(Item).one()
item.is_container = True
db_session.commit()
subitem_response = create_subitem(client, item.id, name="Bulb")
- assert item_response.headers["location"] == f"/items/{item.id}"
+ assert item_response.headers["location"] == f"/boxes/{box.id}"
assert subitem_response.headers["location"] == f"/items/{item.id}"
@@ -730,6 +738,7 @@ def test_new_item_page_shows_clear_context_and_default_quantity(client, db_sessi
assert 'name="quantity"' in response.text
assert 'value="1"' in response.text
assert "这个物品本身是一个小容器" in response.text
+ assert "保存并添加下一个" in response.text
def test_new_subitem_page_shows_clear_context_and_default_quantity(client, db_session):
@@ -746,6 +755,7 @@ def test_new_subitem_page_shows_clear_context_and_default_quantity(client, db_se
assert "文件袋" in response.text
assert 'name="quantity"' in response.text
assert 'value="1"' in response.text
+ assert "保存并添加下一个" in response.text
def test_box_detail_page_renders_clear_hierarchy_and_dense_list_structure(client, db_session):
@@ -775,3 +785,80 @@ def test_item_detail_page_renders_clear_hierarchy(client, db_session):
assert "容器型 Item" in response.text
assert "书房箱" in response.text
assert "SubItem" in response.text
+
+
+def test_creating_regular_item_redirects_back_to_parent_box(client, db_session):
+ box = Box(name="连续录入箱")
+ db_session.add(box)
+ db_session.commit()
+
+ response = create_item(client, box.id, name="剪刀", is_container=False)
+
+ assert response.status_code == 303
+ assert response.headers["location"] == f"/boxes/{box.id}"
+
+
+def test_creating_regular_subitem_redirects_back_to_parent_item(client, db_session):
+ box = Box(name="配件箱")
+ item = Item(name="线材袋", box=box, is_container=True)
+ db_session.add_all([box, item])
+ db_session.commit()
+
+ response = create_subitem(client, item.id, name="USB头")
+
+ assert response.status_code == 303
+ assert response.headers["location"] == f"/items/{item.id}"
+
+
+def test_creating_box_redirects_to_new_box_detail(client):
+ response = create_box(client, name="新箱子")
+
+ assert response.status_code == 303
+ assert response.headers["location"].startswith("/boxes/")
+ assert not response.headers["location"].endswith("/items/new")
+
+
+def test_creating_container_item_redirects_to_item_detail(client, db_session):
+ box = Box(name="子容器箱")
+ db_session.add(box)
+ db_session.commit()
+
+ response = create_item(client, box.id, name="小收纳盒", is_container=True)
+
+ created_item = db_session.query(Item).one()
+ assert response.status_code == 303
+ assert response.headers["location"] == f"/items/{created_item.id}"
+
+
+def test_creating_item_with_save_and_add_next_returns_to_same_new_item_context(client, db_session):
+ box = Box(name="快速录入箱")
+ db_session.add(box)
+ db_session.commit()
+
+ response = create_item(
+ client,
+ box.id,
+ name="袜子",
+ is_container=False,
+ submit_action="save_and_add_next",
+ )
+
+ assert response.status_code == 303
+ assert response.headers["location"] == f"/boxes/{box.id}/items/new"
+
+
+def test_creating_subitem_with_save_and_add_next_returns_to_same_new_subitem_context(client, db_session):
+ box = Box(name="电子箱")
+ item = Item(name="配件袋", box=box, is_container=True)
+ db_session.add_all([box, item])
+ db_session.commit()
+
+ response = create_subitem(
+ client,
+ item.id,
+ name="转接头",
+ submit_action="save_and_add_next",
+ )
+
+ assert response.status_code == 303
+ assert response.headers["location"] == f"/items/{item.id}/subitems/new"