Send tape jobs paced, and carry the manual grouping through

Print, preview, PDF and the redirect back all honour a groups= spec, so
a hand-built label survives printing instead of reverting to drawers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vunz4VLxAYDBnU5tLciLDC
This commit is contained in:
jessikittyandClaude Opus 5 committed 2026-10-13 10:28:09 +11:00
1 parent deb009b80e
commit fb59530f0e
1 file changed
+30 -8
+30 -8
View File
@@ -46,7 +46,15 @@ def printer_config():
} }
def _options_query(options, labels): def _group_spec():
"""
The manual grouping from the page: "1,2|3" means parts 1 and 2 share a
label and part 3 is on its own. Empty means "work it out from drawers".
"""
return (request.values.get('groups') or '').strip()
def _options_query(options, labels, group_spec=None):
""" """
The chosen options as query args, so a redirect back to the tape page The chosen options as query args, so a redirect back to the tape page
comes back exactly as the user left it. Without this, printing bounced comes back exactly as the user left it. Without this, printing bounced
@@ -65,6 +73,8 @@ def _options_query(options, labels):
'align': options.align, 'align': options.align,
'fields': options.fields, 'fields': options.fields,
} }
if group_spec:
query['groups'] = group_spec
# Checkboxes: only present when on, which is what the parser expects. # Checkboxes: only present when on, which is what the parser expects.
if options.rotate: if options.rotate:
query['rotate'] = '1' query['rotate'] = '1'
@@ -115,13 +125,16 @@ def index():
source, rotate_default=config['rotate'], source, rotate_default=config['rotate'],
size_default=config['size_mode'], tape_default=config['tape']) size_default=config['size_mode'], tape_default=config['tape'])
plan = tape_printer.plan_groups(labels, options) group_spec = _group_spec()
plan = tape_printer.plan_groups(labels, options, group_spec)
tape = tape_printer.get_tape(options.tape) tape = tape_printer.get_tape(options.tape)
return render_template( return render_template(
'admin/labels/tape.html', 'admin/labels/tape.html',
labels=labels, labels=labels,
plan=plan, plan=plan,
group_spec=tape_printer.spec_from_groups(
[item['labels'] for item in plan]),
ids=','.join(str(l.id) for l in labels), ids=','.join(str(l.id) for l in labels),
options=options, options=options,
tapes=tape_printer.TAPE_PRESETS, tapes=tape_printer.TAPE_PRESETS,
@@ -235,15 +248,18 @@ def print_tape():
except ipp_client.IPPError as exc: except ipp_client.IPPError as exc:
flash(f'Could not reach the printer: {exc}', 'danger') flash(f'Could not reach the printer: {exc}', 'danger')
return redirect(url_for('tape.index', return redirect(url_for('tape.index',
**_options_query(options, labels))) **_options_query(options, labels,
_group_spec())))
if not mime_type: if not mime_type:
flash('The printer did not offer a format LIM can produce. Use ' flash('The printer did not offer a format LIM can produce. Use '
'Download PDF instead.', 'warning') 'Download PDF instead.', 'warning')
return redirect(url_for('tape.index', return redirect(url_for('tape.index',
**_options_query(options, labels))) **_options_query(options, labels,
_group_spec())))
upload_folder = current_app.config['UPLOAD_FOLDER'] upload_folder = current_app.config['UPLOAD_FOLDER']
plan = tape_printer.plan_groups(labels, options) group_spec = _group_spec()
plan = tape_printer.plan_groups(labels, options, group_spec)
if options.is_fixed and not tape_printer.tape_fits_label(options.tape): if options.is_fixed and not tape_printer.tape_fits_label(options.tape):
flash(f'{tape_printer.LABEL_WIDTH_MM:.0f}mm labels need 50mm tape - ' flash(f'{tape_printer.LABEL_WIDTH_MM:.0f}mm labels need 50mm tape - '
@@ -261,8 +277,13 @@ def print_tape():
document = tape_printer.image_to_bytes(image, mime_type) document = tape_printer.image_to_bytes(image, mime_type)
# copies is handled by repeating the job rather than the IPP # copies is handled by repeating the job rather than the IPP
# copies attribute: tape printers vary wildly in honouring it. # copies attribute: tape printers vary wildly in honouring it.
#
# Paced rather than fired in a burst: the VC-500W takes one job
# at a time and answers "busy" to anything arriving while the
# tape is still moving, so each job waits for the printer to go
# idle and retries while it is still working.
for _ in range(options.copies): for _ in range(options.copies):
ipp_client.print_job( ipp_client.print_job_paced(
host, document, mime_type, host, document, mime_type,
job_name=f'LIM: {name}'[:120], job_name=f'LIM: {name}'[:120],
port=config['port'], path=config['path']) port=config['port'], path=config['path'])
@@ -279,7 +300,8 @@ def print_tape():
for problem in failures[:5]: for problem in failures[:5]:
flash(problem, 'danger') flash(problem, 'danger')
return redirect(url_for('tape.index', **_options_query(options, labels))) return redirect(url_for('tape.index',
**_options_query(options, labels, group_spec)))
@tape_bp.route('/download.pdf', methods=['GET', 'POST']) @tape_bp.route('/download.pdf', methods=['GET', 'POST'])
@@ -298,7 +320,7 @@ def download_pdf():
source, rotate_default=config['rotate'], source, rotate_default=config['rotate'],
size_default=config['size_mode'], tape_default=config['tape']) size_default=config['size_mode'], tape_default=config['tape'])
buffer = tape_printer.render_batch_pdf( buffer = tape_printer.render_batch_pdf(
labels, options, current_app.config['UPLOAD_FOLDER']) labels, options, current_app.config['UPLOAD_FOLDER'], _group_spec())
if buffer is None: if buffer is None:
flash('Nothing to render.', 'warning') flash('Nothing to render.', 'warning')
return redirect(url_for('labels.index')) return redirect(url_for('labels.index'))