Commit initial
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
---
|
||||
phase: 03-printer-configuration
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["03-01"]
|
||||
files_modified:
|
||||
- tests/test_printer_crud.py
|
||||
- imptune/api/printers.py
|
||||
- imptune/api/pages.py
|
||||
- imptune/templates/partials/printer_detail.html
|
||||
autonomous: true
|
||||
requirements:
|
||||
- PRNT-10
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "User can open a saved printer config and see all fields pre-populated"
|
||||
- "User can see the associated driver info on the detail page"
|
||||
- "A regenerate button is visible (disabled/placeholder until Phase 4)"
|
||||
artifacts:
|
||||
- path: "imptune/templates/partials/printer_detail.html"
|
||||
provides: "Printer detail view with all fields and driver info"
|
||||
- path: "imptune/api/printers.py"
|
||||
provides: "GET /printers/{id} detail endpoint"
|
||||
key_links:
|
||||
- from: "imptune/templates/partials/printer_list.html"
|
||||
to: "/printers/{id}"
|
||||
via: "printer name link in list row"
|
||||
pattern: "href.*printers.*id"
|
||||
- from: "imptune/api/printers.py"
|
||||
to: "imptune/db/models.py"
|
||||
via: "Printer.get_by_id with driver FK access"
|
||||
pattern: "Printer\\.get_by_id|printer\\.driver"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Implement the printer detail/edit page so saved configs can be retrieved and prepared for regeneration.
|
||||
|
||||
Purpose: PRNT-10 requires that a user can open a saved printer config and regenerate its package without re-uploading drivers. Phase 3's scope is: the config is fully retrievable, driver FK is intact, and a "Regenerate" button exists (placeholder until Phase 4 delivers script generation). This also adds printer name links in the list for navigation.
|
||||
|
||||
Output: GET /printers/{id} detail page with pre-populated fields, driver info display, and regeneration placeholder button.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/03-printer-configuration/03-RESEARCH.md
|
||||
@.planning/phases/03-printer-configuration/03-01-SUMMARY.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Contracts from Plan 01 that this plan builds on -->
|
||||
|
||||
From imptune/api/printers.py (created in Plan 01):
|
||||
```python
|
||||
router = APIRouter(prefix="/printers")
|
||||
|
||||
def _render_printer_list(request: Request) -> HTMLResponse:
|
||||
"""Returns partials/printer_list.html with grouped printers."""
|
||||
|
||||
def _error_response(message: str, status_code: int = 400) -> HTMLResponse:
|
||||
"""HTMX-friendly error fragment."""
|
||||
```
|
||||
|
||||
From imptune/db/models.py:
|
||||
```python
|
||||
class Printer(BaseModel):
|
||||
name = CharField()
|
||||
ip_address = CharField()
|
||||
port_name = CharField()
|
||||
client = ForeignKeyField(Client, null=True, backref="printers")
|
||||
driver = ForeignKeyField(Driver, null=True, backref="printers")
|
||||
duplex_mode = CharField(default="OneSided")
|
||||
color_mode = BooleanField(default=True)
|
||||
paper_size = CharField(default="A4")
|
||||
collate = BooleanField(default=True)
|
||||
|
||||
class Driver(BaseModel):
|
||||
sha256 = CharField(unique=True)
|
||||
original_filename = CharField()
|
||||
driver_desc = CharField(null=True) # JSON list of driver names
|
||||
```
|
||||
|
||||
From imptune/storage/driver_store.py:
|
||||
```python
|
||||
class DriverStore:
|
||||
def get_path(self, sha256: str) -> Path:
|
||||
"""Returns path to stored driver ZIP."""
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Write failing test for printer detail page</name>
|
||||
<files>tests/test_printer_crud.py</files>
|
||||
<behavior>
|
||||
- test_printer_detail_shows_driver: Create a Driver record (via direct Peewee insert with sha256, original_filename, driver_desc=json.dumps(["HP Universal"])), create a Printer with driver FK set. GET /printers/{id} returns 200 with HTML containing printer name, IP, and "HP Universal" driver name.
|
||||
- test_printer_detail_not_found: GET /printers/9999 returns 404.
|
||||
- test_printer_detail_no_driver: Create a Printer with driver=None. GET /printers/{id} returns 200, HTML does not crash, shows "No driver assigned" or similar.
|
||||
</behavior>
|
||||
<action>
|
||||
Append three new tests to the existing `tests/test_printer_crud.py` file (created in Plan 01):
|
||||
- `test_printer_detail_shows_driver`: Use the `client` fixture. Create a Driver record directly via `Driver.create(sha256="abc123", original_filename="test.zip", size_bytes=1000, driver_desc=json.dumps(["HP Universal"]))`. Create a Printer with `driver=driver_obj`. GET `/printers/{printer.id}` and assert 200 status. Assert "HP Universal" appears in response text. Assert printer name appears.
|
||||
- `test_printer_detail_not_found`: GET `/printers/9999` returns 404.
|
||||
- `test_printer_detail_no_driver`: Create Printer with driver=None. GET `/printers/{printer.id}` returns 200. Assert "No driver assigned" or similar text in response.
|
||||
|
||||
Run tests to confirm RED state (route does not exist yet).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd C:/Users/SebastienQUEROL/Documents/projets/ImpTune && python -m pytest tests/test_printer_crud.py::test_printer_detail_shows_driver tests/test_printer_crud.py::test_printer_detail_not_found tests/test_printer_crud.py::test_printer_detail_no_driver -x -q 2>&1 | head -20</automated>
|
||||
</verify>
|
||||
<done>Three new tests exist and fail (RED state). Existing tests still pass.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Implement printer detail route, template, and list navigation links</name>
|
||||
<files>imptune/api/printers.py, imptune/api/pages.py, imptune/templates/partials/printer_detail.html, imptune/templates/partials/printer_list.html</files>
|
||||
<action>
|
||||
**1. Add GET /printers/{printer_id} to `imptune/api/pages.py`:**
|
||||
- Route: `@router.get("/printers/{printer_id}", response_class=HTMLResponse)`
|
||||
- Handler: `def printer_detail(request: Request, printer_id: int):`
|
||||
- Query: `Printer.select(Printer, Client, Driver).join(Client, JOIN.LEFT_OUTER).switch(Printer).join(Driver, JOIN.LEFT_OUTER).where(Printer.id == printer_id).first()`
|
||||
- If not found: return HTMLResponse with 404 status and a simple error page.
|
||||
- If found: parse `printer.driver.driver_desc` (JSON) into driver_names list if driver exists. Pass `printer`, `driver_names`, and `driver` to template.
|
||||
- Render `printers.html` but with a detail block, OR create a dedicated detail template that extends base.html. Prefer: render `partials/printer_detail.html` inside the printers page layout.
|
||||
|
||||
Actually, simpler approach: create a standalone detail page.
|
||||
- Render: `templates.TemplateResponse(request=request, name="printer_detail.html", context={"printer": printer, "driver_names": driver_names})`
|
||||
- This requires creating `imptune/templates/printer_detail.html` (NOT a partial — a full page).
|
||||
|
||||
**2. Create `imptune/templates/printer_detail.html`:**
|
||||
Extends `base.html`. Content:
|
||||
```
|
||||
<h1>{{ printer.name }}</h1>
|
||||
<article>
|
||||
<h2>Configuration</h2>
|
||||
<dl>
|
||||
<dt>IP Address</dt><dd>{{ printer.ip_address }}</dd>
|
||||
<dt>Port Name</dt><dd>{{ printer.port_name }}</dd>
|
||||
<dt>Duplex Mode</dt><dd>{{ printer.duplex_mode }}</dd>
|
||||
<dt>Color Mode</dt><dd>{{ "Color" if printer.color_mode else "Grayscale" }}</dd>
|
||||
<dt>Paper Size</dt><dd>{{ printer.paper_size }}</dd>
|
||||
<dt>Collate</dt><dd>{{ "Yes" if printer.collate else "No" }}</dd>
|
||||
<dt>Client</dt><dd>{{ printer.client.name if printer.client_id else "Unassigned" }}</dd>
|
||||
</dl>
|
||||
|
||||
<h2>Driver</h2>
|
||||
{% if printer.driver_id %}
|
||||
<dl>
|
||||
<dt>Package</dt><dd>{{ printer.driver.original_filename }}</dd>
|
||||
<dt>Driver Name(s)</dt><dd>{{ driver_names | join(", ") }}</dd>
|
||||
<dt>Architecture</dt><dd>{{ printer.driver.architecture or "Unknown" }}</dd>
|
||||
</dl>
|
||||
{% else %}
|
||||
<p>No driver assigned</p>
|
||||
{% endif %}
|
||||
|
||||
<h2>Actions</h2>
|
||||
<button disabled aria-busy="false" title="Available after script generation is implemented (Phase 4)">
|
||||
Regenerate Package
|
||||
</button>
|
||||
<a href="/printers" role="button" class="secondary">Back to Printers</a>
|
||||
</article>
|
||||
```
|
||||
|
||||
**3. Update `imptune/templates/partials/printer_list.html`:**
|
||||
Make printer names clickable: change the Name `<td>` from plain text to `<a href="/printers/{{ p.id }}">{{ p.name }}</a>`.
|
||||
|
||||
**4. Adjust file path:** The detail template is `imptune/templates/printer_detail.html` (full page, not partial).
|
||||
|
||||
Run all tests to confirm GREEN state.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd C:/Users/SebastienQUEROL/Documents/projets/ImpTune && python -m pytest tests/test_printer_crud.py -x -q && python -m pytest tests/ -v</automated>
|
||||
</verify>
|
||||
<done>All tests pass (GREEN). GET /printers/{id} shows full printer config with driver info. "Regenerate Package" button is visible but disabled. Printer names in list are clickable links to detail page. Full test suite green.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `pytest tests/test_printer_crud.py -x -q` — all tests pass including new detail tests
|
||||
- `pytest tests/ -v` — full suite green
|
||||
- GET /printers/{id} displays all printer fields and driver info
|
||||
- Printer names in list link to detail page
|
||||
- "Regenerate Package" button visible but disabled
|
||||
- 404 returned for nonexistent printer IDs
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- PRNT-10 verified: saved config retrievable with driver association intact, regeneration button present (placeholder)
|
||||
- Detail page shows all configured fields (name, IP, port, duplex, color, paper, collate, client, driver)
|
||||
- Driver info displayed from FK relationship (no re-upload needed)
|
||||
- Full test suite green with no regressions
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/03-printer-configuration/03-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user