ux refine
This commit is contained in:
+18
-2
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -77,6 +77,13 @@
|
||||
</button>
|
||||
</section>
|
||||
{% endif %}
|
||||
<button type="submit">{{ submit_label }}</button>
|
||||
<div class="form-actions">
|
||||
<button type="submit" name="submit_action" value="save">{{ submit_label }}</button>
|
||||
{% if not item %}
|
||||
<button type="submit" name="submit_action" value="save_and_add_next" class="button-secondary">
|
||||
保存并添加下一个
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -72,6 +72,13 @@
|
||||
</button>
|
||||
</section>
|
||||
{% endif %}
|
||||
<button type="submit">{{ submit_label }}</button>
|
||||
<div class="form-actions">
|
||||
<button type="submit" name="submit_action" value="save">{{ submit_label }}</button>
|
||||
{% if not subitem %}
|
||||
<button type="submit" name="submit_action" value="save_and_add_next" class="button-secondary">
|
||||
保存并添加下一个
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
+93
-6
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user