An SVG is an XML document that may carry a script, and it is an accepted image type because floor-plan maps and branding genuinely want vector. Loaded through an img tag that script never runs, so the tiles and maps were never the risk. Opening the file's own URL is - and the application image route is public, so that URL needs no session. Every route that serves an upload now goes through one helper that sends Content-Security-Policy: default-src 'none'; sandbox, and nosniff. Seven routes across core and five plugins, so a new one added later starts from the same place rather than repeating the reasoning. Banning the format instead would have cost the maps their only sensible one. The app also sent no security headers at all. It now sets nosniff, frame-ancestors self (as X-Frame-Options too, for the display bays' browsers) and a referrer policy. Deliberately NOT a page-wide CSP: this serves an SPA with inline styles, so a real script-src policy is a change worth making with the frontend in front of you, and a permissive header claiming one would be worse than having none. Contract 0.19.0. send_upload is on the shopdb.api surface, because a plugin serving user-supplied bytes should not have to remember these headers. The same bump records that get_dashboard_widgets has taken data and shape rather than a component name since the dashboard was rebuilt - that shipped without a bump, while BasePlugin and PLUGIN-HOOKS.md both still documented the shape nothing renders, which is how five plugins came to declare widgets pointing at components nobody had written.
Printedparts plugin
3D-printed parts inventory + kiosk checkout
This plugin was generated by flask plugin new printedparts. It satisfies the framework contract out of the box. Replace the example model and routes with your domain.
What's here
plugin.py- thePrintedpartsPluginclass extendingBasePlugin. Editinit_appfor custom setup,on_installto seed reference data.models/printedparts.py- example Asset extension table. Replaceexamplefieldwith your domain fields.api/routes.py- example list and detail endpoints. Add CRUD as needed.schemas/__init__.py- marshmallow schema stub for request/response validation.tests/test_plugin.py- smoke tests asserting contract compliance.manifest.json- plugin metadata. Bumpversionon changes; keepcore_versionrange broad.
Common edits
| You want to... | Do this |
|---|---|
| Add a hook (search, navigation, dashboard widget) | Override the method in PrintedpartsPlugin. See docs/PLUGIN-HOOKS.md. |
| Accept external collector data | Override get_collector_schema() to return a JSON Schema. See ADR-006. |
| Add another model | Create models/<other>.py, export it in models/__init__.py, return it in get_models(). |
| Add a CLI command | Override get_cli_commands() returning a list of Click commands. |
Frontend
Vue components for this plugin live under frontend/src/views/printedparts/ (per project convention). Backend scaffolding does not generate frontend yet; copy from an existing plugin's view files (e.g., frontend/src/views/network/) as a starting point.
Install and run
flask plugin install printedparts
flask db migrate -m "Add printedparts plugin tables"
flask db upgrade
pytest plugins/printedparts/tests/
References
docs/PLUGIN-HOOKS.md- canonical hook referencedocs/PLUGIN-QUICKSTART.md- 30-minute walkthroughmigrations/adr/ADR-001-asset-as-platform-contract.md- the platform contractmigrations/adr/ADR-002-plugin-versioning.md- versioning rules
Why the kiosk take endpoint is unauthenticated
POST /api/printedparts/kiosk/take is the product's first open WRITE (every
other kiosk endpoint is a read). Accepted deliberately, against the criteria
in docs/proposals/printedparts-plugin.md:
- Decrement-only: it can reduce stock of an active item, nothing else.
- Fully attributed: it refuses to act without a badge that resolves under the site policy; every action lands in the ledger with SSO + name + time.
- Bounded blast radius: worst case is stock counts driven low - visible in the ledger and reversible with an adjust.
- Physically rate-limited: it serves a touch screen on the shop floor; nothing enumerable, nothing worth scraping.
Any future open-write endpoint must clear the same bar.