Adding ability to add attachment points when editing augmented part
This commit is contained in:
1 parent
ceae27a12e
commit
d67b4ceb65
7 files changed
+355
-1
No files matched your search
@@ -16,6 +16,17 @@ async def get(db: AsyncSession, attachment_point_id: int) -> AttachmentPoint | N
|
|||||||
return await db.get(AttachmentPoint, attachment_point_id)
|
return await db.get(AttachmentPoint, attachment_point_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def list_for_augmented_part(
|
||||||
|
db: AsyncSession, augmented_part_id: int
|
||||||
|
) -> list[AttachmentPoint]:
|
||||||
|
result = await db.execute(
|
||||||
|
select(AttachmentPoint)
|
||||||
|
.where(AttachmentPoint.augmented_part_id == augmented_part_id)
|
||||||
|
.order_by(AttachmentPoint.id)
|
||||||
|
)
|
||||||
|
return list(result.scalars().all())
|
||||||
|
|
||||||
|
|
||||||
async def list_all(
|
async def list_all(
|
||||||
db: AsyncSession, skip: int = 0, limit: int = 100
|
db: AsyncSession, skip: int = 0, limit: int = 100
|
||||||
) -> list[AttachmentPoint]:
|
) -> list[AttachmentPoint]:
|
||||||
|
|||||||
@@ -366,6 +366,40 @@ input[type=password] {
|
|||||||
align-items: end;
|
align-items: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Inline attachment-points sub-section inside an edit row */
|
||||||
|
.ap-inline-section {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-inline-section h3 {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.3rem 0;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-item:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-item-name { font-weight: 500; flex: 0 0 auto; }
|
||||||
|
.ap-item-type { color: var(--muted); flex: 0 0 auto; font-size: 0.8rem; }
|
||||||
|
.ap-item-conn { flex: 1; }
|
||||||
|
.ap-item .row-actions { margin-left: auto; }
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
.btn {
|
.btn {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{% if apoint.iso_thread %}
|
||||||
|
{% set conn_type = "ISO" %}
|
||||||
|
{% set conn_label = "M" ~ apoint.iso_thread.diameter_mm ~ "×" ~ apoint.iso_thread.pitch_mm %}
|
||||||
|
{% elif apoint.unc_thread %}
|
||||||
|
{% set conn_type = "UNC" %}
|
||||||
|
{% set conn_label = apoint.unc_thread.diameter_fraction ~ "-" ~ apoint.unc_thread.tpi ~ " " ~ apoint.unc_thread.series.value %}
|
||||||
|
{% elif apoint.clamp_connection %}
|
||||||
|
{% set conn_type = "Clamp" %}
|
||||||
|
{% if apoint.clamp_connection.min_mm is not none %}
|
||||||
|
{% set conn_label = apoint.clamp_connection.min_mm ~ "–" ~ apoint.clamp_connection.max_mm ~ " mm" %}
|
||||||
|
{% else %}
|
||||||
|
{% set conn_label = apoint.clamp_connection.min_in ~ "–" ~ apoint.clamp_connection.max_in ~ " in" %}
|
||||||
|
{% endif %}
|
||||||
|
{% elif apoint.static_connection %}
|
||||||
|
{% set conn_type = "Static" %}
|
||||||
|
{% set conn_label = apoint.static_connection.od_mm ~ " mm OD" %}
|
||||||
|
{% else %}
|
||||||
|
{% set conn_type = "—" %}
|
||||||
|
{% set conn_label = "—" %}
|
||||||
|
{% endif %}
|
||||||
|
<div id="augmented-part-ap-{{ apoint.id }}" class="ap-item">
|
||||||
|
<span class="ap-item-name">{{ apoint.attachment_point_name.name }}</span>
|
||||||
|
<span class="ap-item-type">{{ conn_type }}</span>
|
||||||
|
<code class="ap-item-conn">{{ conn_label }}</code>
|
||||||
|
<div class="row-actions">
|
||||||
|
<button class="btn btn-ghost"
|
||||||
|
hx-get="/admin/augmented-parts/{{ apoint.augmented_part_id }}/attachment-points/{{ apoint.id }}/edit"
|
||||||
|
hx-target="#augmented-part-ap-{{ apoint.id }}"
|
||||||
|
hx-swap="outerHTML">Edit</button>
|
||||||
|
<button class="btn btn-danger"
|
||||||
|
hx-delete="/admin/augmented-parts/{{ apoint.augmented_part_id }}/attachment-points/{{ apoint.id }}"
|
||||||
|
hx-target="#ap-{{ apoint.augmented_part_id }}-attachment-points"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-confirm="Delete this attachment point?">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
{% if apoint.iso_thread_id %}{% set current_type = "iso_thread" %}
|
||||||
|
{% elif apoint.unc_thread_id %}{% set current_type = "unc_thread" %}
|
||||||
|
{% elif apoint.clamp_connection_id %}{% set current_type = "clamp_connection" %}
|
||||||
|
{% elif apoint.static_connection_id %}{% set current_type = "static_connection" %}
|
||||||
|
{% else %}{% set current_type = "" %}{% endif %}
|
||||||
|
{# _attachment_point_connection_select.html expects `ap` for pre-selection #}
|
||||||
|
{% set ap = apoint %}
|
||||||
|
{% set connection_type = current_type %}
|
||||||
|
<div id="augmented-part-ap-{{ apoint.id }}">
|
||||||
|
<form hx-post="/admin/augmented-parts/{{ apoint.augmented_part_id }}/attachment-points/{{ apoint.id }}"
|
||||||
|
hx-target="#augmented-part-ap-{{ apoint.id }}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
class="inline-edit" style="padding:0.3rem 0">
|
||||||
|
<label>Name
|
||||||
|
<select name="attachment_point_name_id" required>
|
||||||
|
{% for apn in attachment_point_names %}
|
||||||
|
<option value="{{ apn.id }}" {{ "selected" if apn.id == apoint.attachment_point_name_id }}>{{ apn.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>Type
|
||||||
|
<select name="connection_type" required
|
||||||
|
hx-get="/admin/attachment-points/connection-options"
|
||||||
|
hx-target="#edit-ap-{{ apoint.id }}-connection-options"
|
||||||
|
hx-trigger="change"
|
||||||
|
hx-include="[name='connection_type']">
|
||||||
|
<option value="">— type —</option>
|
||||||
|
<option value="iso_thread" {{ "selected" if current_type == "iso_thread" }}>ISO Thread</option>
|
||||||
|
<option value="unc_thread" {{ "selected" if current_type == "unc_thread" }}>UNC Thread</option>
|
||||||
|
<option value="clamp_connection" {{ "selected" if current_type == "clamp_connection" }}>Clamp Connection</option>
|
||||||
|
<option value="static_connection" {{ "selected" if current_type == "static_connection" }}>Static Connection</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div id="edit-ap-{{ apoint.id }}-connection-options">
|
||||||
|
{% include "admin/_attachment_point_connection_select.html" %}
|
||||||
|
</div>
|
||||||
|
<div class="row-actions">
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
<button type="button" class="btn btn-ghost"
|
||||||
|
hx-get="/admin/augmented-parts/{{ apoint.augmented_part_id }}/attachment-points/{{ apoint.id }}/row"
|
||||||
|
hx-target="#augmented-part-ap-{{ apoint.id }}"
|
||||||
|
hx-swap="outerHTML">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<div id="ap-{{ augmented_part_id }}-attachment-points" class="ap-inline-section">
|
||||||
|
<h3>Attachment Points</h3>
|
||||||
|
|
||||||
|
{% for apoint in attachment_points_list %}
|
||||||
|
{% include "admin/_augmented_part_ap_row.html" %}
|
||||||
|
{% else %}
|
||||||
|
<p class="muted" style="font-size:0.85rem;margin:0 0 0.5rem">No attachment points yet.</p>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<form hx-post="/admin/augmented-parts/{{ augmented_part_id }}/attachment-points"
|
||||||
|
hx-target="#ap-{{ augmented_part_id }}-attachment-points"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
class="inline-edit" style="margin-top:0.5rem">
|
||||||
|
<label>Name
|
||||||
|
<select name="attachment_point_name_id" required>
|
||||||
|
<option value="">— select —</option>
|
||||||
|
{% for apn in attachment_point_names %}
|
||||||
|
<option value="{{ apn.id }}">{{ apn.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>Type
|
||||||
|
<select name="connection_type" required
|
||||||
|
hx-get="/admin/attachment-points/connection-options"
|
||||||
|
hx-target="#ap-{{ augmented_part_id }}-add-connection-options"
|
||||||
|
hx-trigger="change"
|
||||||
|
hx-include="[name='connection_type']">
|
||||||
|
<option value="">— type —</option>
|
||||||
|
<option value="iso_thread">ISO Thread</option>
|
||||||
|
<option value="unc_thread">UNC Thread</option>
|
||||||
|
<option value="clamp_connection">Clamp Connection</option>
|
||||||
|
<option value="static_connection">Static Connection</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div id="ap-{{ augmented_part_id }}-add-connection-options"></div>
|
||||||
|
<button type="submit" class="btn btn-primary">Add</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<tr id="augmented-part-{{ ap.id }}">
|
<tr id="augmented-part-{{ ap.id }}">
|
||||||
<td colspan="9">
|
<td colspan="9">
|
||||||
<form hx-post="/admin/augmented-parts/{{ ap.id }}"
|
<form id="augmented-part-edit-form-{{ ap.id }}"
|
||||||
|
hx-post="/admin/augmented-parts/{{ ap.id }}"
|
||||||
hx-target="#augmented-part-{{ ap.id }}"
|
hx-target="#augmented-part-{{ ap.id }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
class="inline-edit">
|
class="inline-edit">
|
||||||
@@ -52,5 +53,10 @@
|
|||||||
hx-swap="outerHTML">Cancel</button>
|
hx-swap="outerHTML">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div hx-get="/admin/augmented-parts/{{ ap.id }}/attachment-points"
|
||||||
|
hx-trigger="load"
|
||||||
|
hx-swap="outerHTML">
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1385,6 +1385,190 @@ 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:
|
||||||
|
return {
|
||||||
|
"augmented_part_id": augmented_part_id,
|
||||||
|
"attachment_points_list": await attachment_point_crud.list_for_augmented_part(
|
||||||
|
db, augmented_part_id
|
||||||
|
),
|
||||||
|
"attachment_point_names": await apn_crud.list_all(db),
|
||||||
|
"iso_threads": await iso_thread_crud.list_all(db),
|
||||||
|
"unc_threads": await unc_thread_crud.list_all(db),
|
||||||
|
"clamp_connections": await clamp_connection_crud.list_all(db),
|
||||||
|
"static_connections": await static_connection_crud.list_all(db),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/augmented-parts/{augmented_part_id}/attachment-points", response_class=HTMLResponse
|
||||||
|
)
|
||||||
|
async def augmented_part_attachment_points(
|
||||||
|
request: Request,
|
||||||
|
augmented_part_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
_: User = Depends(web_require("attachment_points:read")),
|
||||||
|
):
|
||||||
|
ctx = await _augmented_part_ap_ctx(db, augmented_part_id)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request, "admin/_augmented_part_attachment_points.html", ctx
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/augmented-parts/{augmented_part_id}/attachment-points", response_class=HTMLResponse
|
||||||
|
)
|
||||||
|
async def create_augmented_part_attachment_point(
|
||||||
|
request: Request,
|
||||||
|
augmented_part_id: int,
|
||||||
|
attachment_point_name_id: int = Form(...),
|
||||||
|
connection_type: str = Form(""),
|
||||||
|
iso_thread_id: str = Form(""),
|
||||||
|
unc_thread_id: str = Form(""),
|
||||||
|
clamp_connection_id: str = Form(""),
|
||||||
|
static_connection_id: str = Form(""),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
actor: User = Depends(web_require("attachment_points:write")),
|
||||||
|
):
|
||||||
|
apoint = await attachment_point_crud.create(
|
||||||
|
db,
|
||||||
|
augmented_part_id=augmented_part_id,
|
||||||
|
attachment_point_name_id=attachment_point_name_id,
|
||||||
|
iso_thread_id=int(iso_thread_id) if iso_thread_id else None,
|
||||||
|
unc_thread_id=int(unc_thread_id) if unc_thread_id else None,
|
||||||
|
clamp_connection_id=int(clamp_connection_id) if clamp_connection_id else None,
|
||||||
|
static_connection_id=int(static_connection_id) if static_connection_id else None,
|
||||||
|
)
|
||||||
|
await audit_crud.log(
|
||||||
|
db, actor=actor, action="create", resource_type="AttachmentPoint",
|
||||||
|
resource_id=apoint.id,
|
||||||
|
description=audit_crud.describe_create(
|
||||||
|
"AttachmentPoint", apoint.id,
|
||||||
|
f"part {augmented_part_id}"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
ctx = await _augmented_part_ap_ctx(db, augmented_part_id)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request, "admin/_augmented_part_attachment_points.html", ctx
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/augmented-parts/{augmented_part_id}/attachment-points/{attachment_point_id}/edit",
|
||||||
|
response_class=HTMLResponse,
|
||||||
|
)
|
||||||
|
async def edit_augmented_part_attachment_point(
|
||||||
|
request: Request,
|
||||||
|
augmented_part_id: int,
|
||||||
|
attachment_point_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
_: User = Depends(web_require("attachment_points:write")),
|
||||||
|
):
|
||||||
|
apoint = await attachment_point_crud.get(db, attachment_point_id)
|
||||||
|
if apoint is None:
|
||||||
|
raise HTTPException(status_code=404, detail="Attachment point not found")
|
||||||
|
ctx = await _augmented_part_ap_ctx(db, augmented_part_id)
|
||||||
|
ctx["apoint"] = apoint
|
||||||
|
if apoint.iso_thread_id:
|
||||||
|
ctx["connection_type"] = "iso_thread"
|
||||||
|
elif apoint.unc_thread_id:
|
||||||
|
ctx["connection_type"] = "unc_thread"
|
||||||
|
elif apoint.clamp_connection_id:
|
||||||
|
ctx["connection_type"] = "clamp_connection"
|
||||||
|
elif apoint.static_connection_id:
|
||||||
|
ctx["connection_type"] = "static_connection"
|
||||||
|
else:
|
||||||
|
ctx["connection_type"] = ""
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request, "admin/_augmented_part_ap_row_edit.html", ctx
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/augmented-parts/{augmented_part_id}/attachment-points/{attachment_point_id}/row",
|
||||||
|
response_class=HTMLResponse,
|
||||||
|
)
|
||||||
|
async def augmented_part_attachment_point_row(
|
||||||
|
request: Request,
|
||||||
|
augmented_part_id: int,
|
||||||
|
attachment_point_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
_: User = Depends(web_require("attachment_points:read")),
|
||||||
|
):
|
||||||
|
apoint = await attachment_point_crud.get(db, attachment_point_id)
|
||||||
|
if apoint is None:
|
||||||
|
raise HTTPException(status_code=404, detail="Attachment point not found")
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request, "admin/_augmented_part_ap_row.html", {"apoint": apoint}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/augmented-parts/{augmented_part_id}/attachment-points/{attachment_point_id}",
|
||||||
|
response_class=HTMLResponse,
|
||||||
|
)
|
||||||
|
async def update_augmented_part_attachment_point(
|
||||||
|
request: Request,
|
||||||
|
augmented_part_id: int,
|
||||||
|
attachment_point_id: int,
|
||||||
|
attachment_point_name_id: int = Form(...),
|
||||||
|
connection_type: str = Form(""),
|
||||||
|
iso_thread_id: str = Form(""),
|
||||||
|
unc_thread_id: str = Form(""),
|
||||||
|
clamp_connection_id: str = Form(""),
|
||||||
|
static_connection_id: str = Form(""),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
actor: User = Depends(web_require("attachment_points:write")),
|
||||||
|
):
|
||||||
|
apoint = await attachment_point_crud.get(db, attachment_point_id)
|
||||||
|
if apoint is None:
|
||||||
|
raise HTTPException(status_code=404, detail="Attachment point not found")
|
||||||
|
changes = {
|
||||||
|
"attachment_point_name_id": attachment_point_name_id,
|
||||||
|
"iso_thread_id": int(iso_thread_id) if iso_thread_id else None,
|
||||||
|
"unc_thread_id": int(unc_thread_id) if unc_thread_id else None,
|
||||||
|
"clamp_connection_id": int(clamp_connection_id) if clamp_connection_id else None,
|
||||||
|
"static_connection_id": int(static_connection_id) if static_connection_id else None,
|
||||||
|
}
|
||||||
|
apoint = await attachment_point_crud.update(db, apoint, changes)
|
||||||
|
await audit_crud.log(
|
||||||
|
db, actor=actor, action="update", resource_type="AttachmentPoint",
|
||||||
|
resource_id=attachment_point_id,
|
||||||
|
description=audit_crud.describe_update(
|
||||||
|
"AttachmentPoint", attachment_point_id, changes
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request, "admin/_augmented_part_ap_row.html", {"apoint": apoint}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/augmented-parts/{augmented_part_id}/attachment-points/{attachment_point_id}",
|
||||||
|
response_class=HTMLResponse,
|
||||||
|
)
|
||||||
|
async def delete_augmented_part_attachment_point(
|
||||||
|
request: Request,
|
||||||
|
augmented_part_id: int,
|
||||||
|
attachment_point_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
actor: User = Depends(web_require("attachment_points:delete")),
|
||||||
|
):
|
||||||
|
apoint = await attachment_point_crud.get(db, attachment_point_id)
|
||||||
|
if apoint is not None:
|
||||||
|
await attachment_point_crud.delete(db, apoint)
|
||||||
|
await audit_crud.log(
|
||||||
|
db, actor=actor, action="delete", resource_type="AttachmentPoint",
|
||||||
|
resource_id=attachment_point_id,
|
||||||
|
description=audit_crud.describe_delete(
|
||||||
|
"AttachmentPoint", attachment_point_id, f"part {augmented_part_id}"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
ctx = await _augmented_part_ap_ctx(db, augmented_part_id)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request, "admin/_augmented_part_attachment_points.html", ctx
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# --- Part Colours ------------------------------------------------------------
|
# --- Part Colours ------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user