Adding ability to edit attachment points on augmented item details

This commit is contained in:
Ian Roddis committed 2026-07-28 22:04:40 -03:00
1 parent 25fccd617f
commit aef60d77be
5 files changed
+83 -37

No files matched your search

@@ -22,6 +22,7 @@
<span class="ap-item-name">{{ apoint.attachment_point_name.name }}</span> <span class="ap-item-name">{{ apoint.attachment_point_name.name }}</span>
<span class="ap-item-type">{{ conn_type }}</span> <span class="ap-item-type">{{ conn_type }}</span>
<code class="ap-item-conn">{{ conn_label }}</code> <code class="ap-item-conn">{{ conn_label }}</code>
{% if can_write_attachment_points %}
<div class="row-actions"> <div class="row-actions">
<button class="btn btn-ghost" <button class="btn btn-ghost"
hx-get="/admin/augmented-parts/{{ apoint.augmented_part_id }}/attachment-points/{{ apoint.id }}/edit" hx-get="/admin/augmented-parts/{{ apoint.augmented_part_id }}/attachment-points/{{ apoint.id }}/edit"
@@ -33,4 +34,5 @@
hx-swap="outerHTML" hx-swap="outerHTML"
hx-confirm="Delete this attachment point?">Delete</button> hx-confirm="Delete this attachment point?">Delete</button>
</div> </div>
{% endif %}
</div> </div>
@@ -7,6 +7,7 @@
<p class="muted" style="font-size:0.85rem;margin:0 0 0.5rem">No attachment points yet.</p> <p class="muted" style="font-size:0.85rem;margin:0 0 0.5rem">No attachment points yet.</p>
{% endfor %} {% endfor %}
{% if can_write_attachment_points %}
<form hx-post="/admin/augmented-parts/{{ augmented_part_id }}/attachment-points" <form hx-post="/admin/augmented-parts/{{ augmented_part_id }}/attachment-points"
hx-target="#ap-{{ augmented_part_id }}-attachment-points" hx-target="#ap-{{ augmented_part_id }}-attachment-points"
hx-swap="outerHTML" hx-swap="outerHTML"
@@ -35,4 +36,5 @@
<div id="ap-{{ augmented_part_id }}-add-connection-options"></div> <div id="ap-{{ augmented_part_id }}-add-connection-options"></div>
<button type="submit" class="btn btn-primary">Add</button> <button type="submit" class="btn btn-primary">Add</button>
</form> </form>
{% endif %}
</div> </div>
+6 -26
View File
@@ -3,20 +3,6 @@
{% block body %} {% block body %}
{% include "_nav.html" %} {% include "_nav.html" %}
{%- macro conn_desc(point) -%}
{%- if point.iso_thread -%}
ISO M{{ point.iso_thread.diameter_mm }}×{{ point.iso_thread.pitch_mm }}
{%- elif point.unc_thread -%}
UNC {{ point.unc_thread.diameter_fraction }}-{{ point.unc_thread.tpi }} {{ point.unc_thread.series.value }}
{%- elif point.clamp_connection -%}
Clamp {{ point.clamp_connection.min_mm if point.clamp_connection.min_mm is not none else point.clamp_connection.min_in }}–{{ point.clamp_connection.max_mm if point.clamp_connection.max_mm is not none else point.clamp_connection.max_in }}
{%- elif point.static_connection -%}
Static {{ point.static_connection.od_mm }} mm OD
{%- else -%}
—
{%- endif -%}
{%- endmacro -%}
<main class="container"> <main class="container">
<p><a href="/admin/augmented-parts" class="btn btn-ghost">← Augmented Parts</a></p> <p><a href="/admin/augmented-parts" class="btn btn-ghost">← Augmented Parts</a></p>
@@ -62,20 +48,14 @@
</dl> </dl>
</div> </div>
{% endif %} {% endif %}
<div class="card">
<h2>Attachment points</h2>
{% for point in attachment_points %}
<div class="ap-item">
<span class="ap-item-name">{{ point.attachment_point_name.name }}</span>
<code class="ap-item-conn">{{ conn_desc(point) }}</code>
</div>
{% else %}
<p class="muted">No attachment points recorded.</p>
{% endfor %}
</div>
</div> </div>
{% if can_read_attachment_points %}
<div class="card">
{% include "admin/_augmented_part_attachment_points.html" %}
</div>
{% endif %}
{% if can_read_fits %} {% if can_read_fits %}
<div class="card"> <div class="card">
{% include "admin/_augmented_part_fits.html" %} {% include "admin/_augmented_part_fits.html" %}
+43 -11
View File
@@ -1792,17 +1792,26 @@ async def augmented_part_detail(
if ap is None: if ap is None:
raise HTTPException(status_code=404, detail="Augmented part not found") raise HTTPException(status_code=404, detail="Augmented part not found")
can_read_fits = user.is_superuser or "fits:read" in user.permissions can_read_fits = user.is_superuser or "fits:read" in user.permissions
can_read_attachment_points = (
user.is_superuser or "attachment_points:read" in user.permissions
)
ctx = { ctx = {
"user": user, "user": user,
"ap": ap, "ap": ap,
"scope_info": await scope_info_crud.get_by_augmented_part( "scope_info": await scope_info_crud.get_by_augmented_part(
db, augmented_part_id db, augmented_part_id
), ),
"attachment_points": await attachment_point_crud.list_for_augmented_part(
db, augmented_part_id
),
"can_read_fits": can_read_fits, "can_read_fits": can_read_fits,
"can_read_attachment_points": can_read_attachment_points,
} }
if can_read_attachment_points:
ctx.update(
await _augmented_part_ap_ctx(
db,
augmented_part_id,
can_write=_can_write_attachment_points(user),
)
)
if can_read_fits: if can_read_fits:
ctx.update( ctx.update(
await _augmented_part_fits_ctx( await _augmented_part_fits_ctx(
@@ -1909,7 +1918,13 @@ async def delete_augmented_part(
return Response(status_code=status.HTTP_200_OK) return Response(status_code=status.HTTP_200_OK)
async def _augmented_part_ap_ctx(db: AsyncSession, augmented_part_id: int) -> dict: def _can_write_attachment_points(user: User) -> bool:
return user.is_superuser or "attachment_points:write" in user.permissions
async def _augmented_part_ap_ctx(
db: AsyncSession, augmented_part_id: int, *, can_write: bool = True
) -> dict:
return { return {
"augmented_part_id": augmented_part_id, "augmented_part_id": augmented_part_id,
"attachment_points_list": await attachment_point_crud.list_for_augmented_part( "attachment_points_list": await attachment_point_crud.list_for_augmented_part(
@@ -1920,6 +1935,7 @@ async def _augmented_part_ap_ctx(db: AsyncSession, augmented_part_id: int) -> di
"unc_threads": await unc_thread_crud.list_all(db), "unc_threads": await unc_thread_crud.list_all(db),
"clamp_connections": await clamp_connection_crud.list_all(db), "clamp_connections": await clamp_connection_crud.list_all(db),
"static_connections": await static_connection_crud.list_all(db), "static_connections": await static_connection_crud.list_all(db),
"can_write_attachment_points": can_write,
} }
@@ -1931,9 +1947,11 @@ async def augmented_part_attachment_points(
request: Request, request: Request,
augmented_part_id: int, augmented_part_id: int,
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
_: User = Depends(web_require("attachment_points:read")), user: User = Depends(web_require("attachment_points:read")),
): ):
ctx = await _augmented_part_ap_ctx(db, augmented_part_id) ctx = await _augmented_part_ap_ctx(
db, augmented_part_id, can_write=_can_write_attachment_points(user)
)
return templates.TemplateResponse( return templates.TemplateResponse(
request, "admin/_augmented_part_attachment_points.html", ctx request, "admin/_augmented_part_attachment_points.html", ctx
) )
@@ -1976,7 +1994,9 @@ async def create_augmented_part_attachment_point(
"AttachmentPoint", apoint.id, f"part {augmented_part_id}" "AttachmentPoint", apoint.id, f"part {augmented_part_id}"
), ),
) )
ctx = await _augmented_part_ap_ctx(db, augmented_part_id) ctx = await _augmented_part_ap_ctx(
db, augmented_part_id, can_write=_can_write_attachment_points(actor)
)
return templates.TemplateResponse( return templates.TemplateResponse(
request, "admin/_augmented_part_attachment_points.html", ctx request, "admin/_augmented_part_attachment_points.html", ctx
) )
@@ -2022,13 +2042,18 @@ async def augmented_part_attachment_point_row(
augmented_part_id: int, augmented_part_id: int,
attachment_point_id: int, attachment_point_id: int,
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
_: User = Depends(web_require("attachment_points:read")), user: User = Depends(web_require("attachment_points:read")),
): ):
apoint = await attachment_point_crud.get(db, attachment_point_id) apoint = await attachment_point_crud.get(db, attachment_point_id)
if apoint is None: if apoint is None:
raise HTTPException(status_code=404, detail="Attachment point not found") raise HTTPException(status_code=404, detail="Attachment point not found")
return templates.TemplateResponse( return templates.TemplateResponse(
request, "admin/_augmented_part_ap_row.html", {"apoint": apoint} request,
"admin/_augmented_part_ap_row.html",
{
"apoint": apoint,
"can_write_attachment_points": _can_write_attachment_points(user),
},
) )
@@ -2075,7 +2100,12 @@ async def update_augmented_part_attachment_point(
), ),
) )
return templates.TemplateResponse( return templates.TemplateResponse(
request, "admin/_augmented_part_ap_row.html", {"apoint": apoint} request,
"admin/_augmented_part_ap_row.html",
{
"apoint": apoint,
"can_write_attachment_points": _can_write_attachment_points(actor),
},
) )
@@ -2103,7 +2133,9 @@ async def delete_augmented_part_attachment_point(
"AttachmentPoint", attachment_point_id, f"part {augmented_part_id}" "AttachmentPoint", attachment_point_id, f"part {augmented_part_id}"
), ),
) )
ctx = await _augmented_part_ap_ctx(db, augmented_part_id) ctx = await _augmented_part_ap_ctx(
db, augmented_part_id, can_write=_can_write_attachment_points(actor)
)
return templates.TemplateResponse( return templates.TemplateResponse(
request, "admin/_augmented_part_attachment_points.html", ctx request, "admin/_augmented_part_attachment_points.html", ctx
) )
+30
View File
@@ -91,6 +91,36 @@ async def test_detail_shows_attributes_and_fits_readonly(
assert "Add fit" not in body assert "Add fit" not in body
assert "Mark Exact" not in body assert "Mark Exact" not in body
assert "Recompute possible fits" not in body assert "Recompute possible fits" not in body
# ...and attachment-point add/edit/delete controls are hidden too
assert 'name="attachment_point_name_id"' not in body
assert "Delete this attachment point?" not in body
@pytest.mark.asyncio
async def test_detail_write_controls_for_attachment_point_editor(
client, seeded, make_user, db_sessionmaker
):
async with db_sessionmaker() as db:
ids = await _seed_part_with_fit(db)
perms = seeded["perms"]
editor = await role_crud.create(
db,
name="ap-editor",
permission_ids=[
perms["augmented_parts:read"].id,
perms["attachment_points:read"].id,
perms["attachment_points:write"].id,
],
)
editor_id = editor.id
await make_user("apeditor@example.com", role_ids=[editor_id])
await _web_login(client, "apeditor@example.com")
resp = await client.get(f"/admin/augmented-parts/{ids['scope_id']}")
assert resp.status_code == 200
body = resp.text
assert 'name="attachment_point_name_id"' in body
assert "Delete this attachment point?" in body
@pytest.mark.asyncio @pytest.mark.asyncio