feat(11-01): UIE-02 — dedicated Add Printer page at GET /printers/new

- Create imptune/templates/printers_new.html: full page extending base.html
  with plain <form action="/printers" method="post"> (no HTMX, browser follows 303)
  and inline driver upload sub-form
- Update imptune/templates/printers.html: remove inline Add Printer form,
  add <a href="/printers/new" role="button">Add Printer</a> link
- Add GET /printers/new route in imptune/api/pages.py (placed before /{printer_id})
- Change POST /printers to always return RedirectResponse(url='/printers', status_code=303)
- Update existing POST /printers tests to assert 303 + follow_redirects=False
- Update test_printer_form.py to check /printers/new instead of /printers (UIE-02 arch)
- UIE-02 tests GREEN; UIE-01/03 scaffolds remain RED (expected — not yet implemented)
This commit is contained in:
2026-04-15 11:03:41 +02:00
parent a02df7d292
commit 3d2cdc4e77
6 changed files with 151 additions and 42 deletions
+17
View File
@@ -85,6 +85,23 @@ def printers_page(request: Request):
)
@router.get("/printers/new", response_class=HTMLResponse)
def printers_new_page(request: Request):
from imptune.db.models import Client, Driver
clients = list(Client.select().order_by(Client.name))
all_drivers = list(Driver.select().order_by(Driver.uploaded_at.desc()))
driver_data = [
{"driver": d, "names": json.loads(d.driver_desc) if d.driver_desc else []}
for d in all_drivers
]
return templates.TemplateResponse(
request=request,
name="printers_new.html",
context={"clients": clients, "driver_data": driver_data},
)
@router.get("/printers/{printer_id}", response_class=HTMLResponse)
def printer_detail(request: Request, printer_id: int):
from imptune.db.models import Client, Driver, Icon, Printer
+2 -2
View File
@@ -5,7 +5,7 @@ from collections import defaultdict
from pathlib import Path
from fastapi import APIRouter, Form, Request
from fastapi.responses import HTMLResponse
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from peewee import JOIN
@@ -101,7 +101,7 @@ def create_printer(
driver=driver_fk,
)
return _render_printer_list(request)
return RedirectResponse(url="/printers", status_code=303)
@router.delete("/{printer_id}", response_class=HTMLResponse)
+1 -4
View File
@@ -3,10 +3,7 @@
{% block content %}
<h1>Printers</h1>
<section>
<h2>Add Printer</h2>
{% include "partials/printer_form.html" %}
</section>
<p><a href="/printers/new" role="button">Add Printer</a></p>
<section>
<h2>Printer Library</h2>
+100
View File
@@ -0,0 +1,100 @@
{% extends "base.html" %}
{% block content %}
<h1>Add Printer</h1>
<p><a href="/printers">&larr; Back to Printer Library</a></p>
<div x-data="{ ip: '', port: '', portEdited: false }">
<form action="/printers" method="post">
<label>
Printer Name
<input type="text" name="name" placeholder="e.g. HP LaserJet 4050" required>
</label>
<label>
IP Address
<input type="text" name="ip_address"
x-model="ip"
@input="if (!portEdited) port = 'IP_' + ip.replaceAll('.', '_')"
placeholder="e.g. 192.168.1.100"
required>
</label>
<label>
Port Name
<input type="text" name="port_name"
x-model="port"
@change="portEdited = true"
@keydown="portEdited = true"
placeholder="e.g. IP_192_168_1_100">
</label>
<label>
Driver
<select name="driver_id" id="printer-form-driver-select">
<option value="">-- No driver --</option>
{% for item in driver_data %}
<option value="{{ item.driver.id }}">
{{ item.driver.original_filename }} ({{ item.names | join(', ') }})
</option>
{% endfor %}
</select>
</label>
<label>
Duplex Mode
<select name="duplex_mode">
<option value="OneSided" selected>One-Sided</option>
<option value="LongEdge">Long Edge</option>
<option value="ShortEdge">Short Edge</option>
</select>
</label>
<label>
<input type="checkbox" name="color_mode" value="on" checked>
Color Mode
</label>
<label>
Paper Size
<select name="paper_size">
<option value="A4" selected>A4</option>
<option value="Letter">Letter</option>
<option value="Legal">Legal</option>
</select>
</label>
<label>
<input type="checkbox" name="collate" value="on" checked>
Collate
</label>
<label>
Client
<select name="client_id">
<option value="">-- Unassigned --</option>
{% for c in clients %}
<option value="{{ c.id }}">{{ c.name }}</option>
{% endfor %}
</select>
</label>
<button type="submit">Save Printer</button>
</form>
<form hx-post="/drivers/upload"
hx-target="#driver-list"
hx-encoding="multipart/form-data"
hx-swap="outerHTML">
<input type="hidden" name="caller" value="printer_form">
<label>
Upload New Driver
<input type="file" name="file" accept=".zip" required>
</label>
<button type="submit" class="secondary">Upload Driver</button>
</form>
<div id="driver-list" style="display:none"></div>
</div>
{% endblock %}