From 4a1bfc4acdb5f66874e98c6aa96ad508c05a0b37 Mon Sep 17 00:00:00 2001 From: jessikitty Date: Mon, 12 Oct 2026 22:08:51 +1100 Subject: [PATCH] Fix: unticking "Rotate for tape feed" had no effect An unticked checkbox is absent from a form submission entirely, so "rotate unticked" and "rotate never mentioned" arrived at the parser looking identical - and rotate defaults to on, so it snapped straight back every time. Add a hidden marker the options form always sends, so absent-but-submitted can be read as unticked. Same bug silently reset the field list when every field was unticked; that is fixed by the same marker. rotate_default lets a saved site-wide preference apply on a fresh arrival without overriding an explicit untick on the page. --- app/services/tape_printer.py | 54 ++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/app/services/tape_printer.py b/app/services/tape_printer.py index 794d105..d008051 100644 --- a/app/services/tape_printer.py +++ b/app/services/tape_printer.py @@ -14,9 +14,9 @@ VC-500W facts this is built around: ORIENTATION: the tape feeds lengthwise, so the raster sent to the printer is normally rotated 90 degrees - the image is tape-width across and -label-length tall. That is the default. If your first test print comes out -sideways, untick "Rotate for tape feed" and print again; it is the one -setting worth checking on a new printer. +label-length tall. That is the default. If your prints come out sideways, +untick "Rotate for tape feed"; to make that permanent, turn off the default +orientation in Label Settings. Created by: Jess Rogerson (yelling commands at Claude.AI) """ @@ -73,6 +73,16 @@ TAPE_PRESETS = { DEFAULT_TAPE = 'cz25' +# What a label shows when you have not said otherwise. Defined once: the +# dataclass default and the form parser both read it, so they cannot drift. +DEFAULT_TAPE_FIELDS = ['image', 'name', 'dimensions', 'part_number', 'accent'] + +# Hidden marker every options form submits. Its presence is how we tell +# "submitted with the box unticked" from "arrived here with no options at +# all" - an unticked checkbox sends nothing, so without this they look +# identical and a default-on box can never be turned off. +FORM_MARKER = '_opts' + FONT_CANDIDATES = { False: [ # regular 'C:\\Windows\\Fonts\\segoeui.ttf', @@ -114,8 +124,8 @@ def load_font(size_px, bold=False): @dataclass class TapeOptions: tape: str = DEFAULT_TAPE - fields: List[str] = field(default_factory=lambda: [ - 'image', 'name', 'dimensions', 'part_number', 'accent']) + fields: List[str] = field( + default_factory=lambda: list(DEFAULT_TAPE_FIELDS)) rotate: bool = True copies: int = 1 # Fixed length in mm, or None to size each label to its content. @@ -131,8 +141,21 @@ def get_tape(key): return TAPE_PRESETS.get(key or DEFAULT_TAPE, TAPE_PRESETS[DEFAULT_TAPE]) -def options_from_form(form): - """Build TapeOptions from request.form / request.args.""" +def options_from_form(form, rotate_default=True): + """ + Build TapeOptions from request.form / request.args. + + An unticked checkbox is simply absent from a form submission - the + browser sends nothing for it. So "rotate unticked" and "rotate never + mentioned" arrive here looking exactly the same, and a box that + defaults to on could never be turned off. FORM_MARKER is a hidden + field the options form always sends: when it is present we know every + checkbox was on screen, so absent means the user unticked it. + + rotate_default applies only when the form was NOT submitted (a fresh + arrival), so the saved site-wide preference is honoured there. + """ + submitted = FORM_MARKER in form def number(key, default, cast=float): try: @@ -141,11 +164,15 @@ def options_from_form(form): return default def flag(key, default=False): - if key not in form: - return default - return str(form.get(key, '')).lower() in ('1', 'true', 'on', 'yes') + if key in form: + return str(form.get(key, '')).lower() in ('1', 'true', 'on', 'yes') + # Submitted without it: the user unticked it. Not submitted: default. + return False if submitted else default + + fields = list(form.getlist('fields')) if hasattr(form, 'getlist') else [] + if not fields and not submitted: + fields = list(DEFAULT_TAPE_FIELDS) - fields = form.getlist('fields') if hasattr(form, 'getlist') else None length = (form.get('fixed_length_mm') or '').strip() if hasattr(form, 'get') else '' try: fixed = float(length) if length else None @@ -154,9 +181,8 @@ def options_from_form(form): return TapeOptions( tape=form.get('tape') or DEFAULT_TAPE, - fields=fields if fields else ['image', 'name', 'dimensions', - 'part_number', 'accent'], - rotate=flag('rotate', True), + fields=fields, + rotate=flag('rotate', rotate_default), copies=max(1, min(99, number('copies', 1, int))), fixed_length_mm=fixed, padding_mm=max(0.0, min(10.0, number('padding_mm', 1.5))),