Replace combined filament dropdown with separate material + color dropdowns

- app/constants.py: MATERIALS and COLORS standard value lists
- Migration 0008: job_material + job_color columns on print_jobs
- Job creation (both /jobs and /brackets/{id}/jobs) stores these independently
  of the filament inventory
- All job tables updated to display job_material · job_color

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martin Hohenberg
2026-06-18 22:08:29 +02:00
parent dc73c9a8c1
commit 6868862d77
6 changed files with 79 additions and 24 deletions

View File

@@ -0,0 +1,26 @@
"""add job_material and job_color to print_jobs
Revision ID: 0008
Revises: 0007
Create Date: 2026-06-18
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
revision: str = "0008"
down_revision: Union[str, None] = "0007"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column("print_jobs", sa.Column("job_material", sa.String(50), nullable=True))
op.add_column("print_jobs", sa.Column("job_color", sa.String(100), nullable=True))
def downgrade() -> None:
op.drop_column("print_jobs", "job_color")
op.drop_column("print_jobs", "job_material")

9
app/constants.py Normal file
View File

@@ -0,0 +1,9 @@
MATERIALS = ["PLA", "PETG", "ABS", "ASA", "TPU", "Nylon", "PC", "Other"]
COLORS = [
"Black", "White", "Grey", "Silver",
"Red", "Orange", "Yellow",
"Green", "Blue", "Purple", "Pink",
"Brown", "Beige", "Natural",
"Transparent", "Translucent",
]

View File

@@ -28,6 +28,8 @@ class PrintJob(Base):
name: Mapped[str] = mapped_column(String(200))
customer: Mapped[str | None] = mapped_column(String(200))
file_name: Mapped[str | None] = mapped_column(String(255))
job_material: Mapped[str | None] = mapped_column(String(50))
job_color: Mapped[str | None] = mapped_column(String(100))
status: Mapped[JobStatus] = mapped_column(Enum(JobStatus), default=JobStatus.queued)
queue_position: Mapped[int | None] = mapped_column(Integer)
started_at: Mapped[datetime | None] = mapped_column(DateTime)

View File

@@ -18,6 +18,7 @@ from app.models.printer_log import PrinterLog
from app.models.printer_type import PrinterType
from app.models.pricing import PricingConfig
from app.models.todo import Todo
from app.constants import COLORS, MATERIALS
from app.stl import get_stl_dir, list_stl_files
from app.stl_analysis import analyse, get_cache_map
@@ -140,6 +141,8 @@ def jobs_page(request: Request, q: str = "", db: Session = Depends(get_db)):
"brackets": db.query(JobBracket).order_by(JobBracket.name).all(),
"stl_files": (stl_files := list_stl_files()),
"stl_cache": get_cache_map(stl_files, db),
"materials": MATERIALS,
"colors": COLORS,
})
@@ -147,7 +150,8 @@ def jobs_page(request: Request, q: str = "", db: Session = Depends(get_db)):
async def create_job(
printer_id: str = Form(""),
customer: str = Form(""),
filament_id: str = Form(""),
job_material: str = Form(""),
job_color: str = Form(""),
bracket_id: str = Form(""),
amount: int = Form(1),
notes: str = Form(""),
@@ -184,9 +188,10 @@ async def create_job(
name=job_name,
file_name=filename,
printer_id=int(printer_id) if printer_id else None,
filament_id=int(filament_id) if filament_id else None,
bracket_id=effective_bracket_id,
customer=customer_str,
job_material=job_material or None,
job_color=job_color or None,
notes=notes.strip() or None,
)
db.add(job)
@@ -236,6 +241,8 @@ def bracket_detail(bracket_id: int, request: Request, db: Session = Depends(get_
"filaments": db.query(Filament).order_by(Filament.material, Filament.color).all(),
"stl_files": (stl_files := list_stl_files()),
"stl_cache": get_cache_map(stl_files, db),
"materials": MATERIALS,
"colors": COLORS,
})
@@ -243,7 +250,8 @@ def bracket_detail(bracket_id: int, request: Request, db: Session = Depends(get_
async def add_jobs_to_bracket(
bracket_id: int,
quantity: int = Form(1),
filament_id: str = Form(""),
job_material: str = Form(""),
job_color: str = Form(""),
notes: str = Form(""),
stl_file: UploadFile = File(None),
stl_select: str = Form(""),
@@ -263,7 +271,8 @@ async def add_jobs_to_bracket(
name=Path(filename).stem if filename else "Unnamed",
file_name=filename,
bracket_id=bracket_id,
filament_id=int(filament_id) if filament_id else None,
job_material=job_material or None,
job_color=job_color or None,
notes=notes.strip() or None,
)
db.add(job)

View File

@@ -53,7 +53,7 @@
</td>
<td class="text-white">{{ j.file_name or j.name }}</td>
<td class="text-secondary">
{% if j.filament %}{{ j.filament.material.value }}{% if j.filament.color %} · {{ j.filament.color }}{% endif %}{% else %}—{% endif %}
{% if j.job_material %}{{ j.job_material }}{% if j.job_color %} · {{ j.job_color }}{% endif %}{% else %}—{% endif %}
</td>
<td class="text-secondary">{{ j.printer.name if j.printer else '—' }}</td>
<td>
@@ -127,16 +127,21 @@
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-secondary small">Material & Color</label>
<select name="filament_id" class="form-select bg-dark border-secondary text-white">
<div class="col-md-4">
<label class="form-label text-secondary small">Material</label>
<select name="job_material" class="form-select bg-dark border-secondary text-white">
<option value="">— none —</option>
{% for f in filaments %}
<option value="{{ f.id }}">{{ f.material.value }}{% if f.color %} · {{ f.color }}{% endif %}{% if f.brand %} ({{ f.brand }}){% endif %}</option>
{% endfor %}
{% for m in materials %}<option>{{ m }}</option>{% endfor %}
</select>
</div>
<div class="col-md-6">
<div class="col-md-4">
<label class="form-label text-secondary small">Color</label>
<select name="job_color" class="form-select bg-dark border-secondary text-white">
<option value="">— none —</option>
{% for c in colors %}<option>{{ c }}</option>{% endfor %}
</select>
</div>
<div class="col-md-4">
<label class="form-label text-secondary small">
Quantity
<span class="text-secondary fw-normal">(copies to create)</span>

View File

@@ -49,7 +49,7 @@
<td class="text-secondary">{{ j.file_name or '—' }}</td>
<td class="text-secondary">{{ j.printer.name if j.printer else '—' }}</td>
<td class="text-secondary">
{% if j.filament %}{{ j.filament.material.value }}{% if j.filament.color %} · {{ j.filament.color }}{% endif %}{% else %}—{% endif %}
{% if j.job_material %}{{ j.job_material }}{% if j.job_color %} · {{ j.job_color }}{% endif %}{% else %}—{% endif %}
</td>
<td>
{% set s = j.status.value %}
@@ -140,17 +140,21 @@
</div>
</div>
<div>
<label class="form-label text-secondary small">Material & Color</label>
<select name="filament_id" class="form-select bg-dark border-secondary text-white">
<option value="">— none —</option>
{% for f in filaments %}
<option value="{{ f.id }}">
{{ f.material.value }}{% if f.color %} · {{ f.color }}{% endif %}
{% if f.brand %} ({{ f.brand }}){% endif %}
</option>
{% endfor %}
</select>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-secondary small">Material</label>
<select name="job_material" class="form-select bg-dark border-secondary text-white">
<option value="">— none —</option>
{% for m in materials %}<option>{{ m }}</option>{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label text-secondary small">Color</label>
<select name="job_color" class="form-select bg-dark border-secondary text-white">
<option value="">— none —</option>
{% for c in colors %}<option>{{ c }}</option>{% endfor %}
</select>
</div>
</div>
<div class="row g-3">