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:
26
alembic/versions/0008_job_material_color.py
Normal file
26
alembic/versions/0008_job_material_color.py
Normal 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
9
app/constants.py
Normal 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",
|
||||
]
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user