Add /admin/labels/dymo
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vunz4VLxAYDBnU5tLciLDC
This commit is contained in:
1 parent
7e74ee56ed
commit
3b6cb77388
1 file changed
+180
@@ -0,0 +1,180 @@
|
||||
"""
|
||||
Dymo LabelWriter printing - admin only.
|
||||
|
||||
The tape printer does one label at a time on a continuous roll. A
|
||||
LabelWriter does die-cut stock, so several drawer labels are ganged onto
|
||||
one sticker and you cut them apart. Same drawer labels, same 40 x 15mm
|
||||
house size, different carrier.
|
||||
|
||||
Routes (all under /admin/labels/dymo):
|
||||
/ choose labels, pick the stock, see the sheet
|
||||
/preview.png one rendered sticker
|
||||
/download.pdf the print-ready PDF, page size = the stock size
|
||||
|
||||
There is no direct printing here on purpose: the LabelWriter 4XL is a USB
|
||||
printer, not a network one, so the PDF goes through whatever driver the
|
||||
machine already has. Print at 100% / Actual size.
|
||||
|
||||
Created by: Jess Rogerson (yelling commands at Claude.AI)
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from flask import (Blueprint, abort, current_app, flash, redirect,
|
||||
render_template, request, send_file, url_for)
|
||||
from flask_login import login_required
|
||||
|
||||
from app.models.site_setting import SiteSetting
|
||||
from app.routes.admin import admin_required
|
||||
from app.routes.labels_tape import _group_spec, _selected_labels
|
||||
from app.services import dymo_sheet, tape_printer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
dymo_bp = Blueprint('dymo', __name__, url_prefix='/admin/labels/dymo')
|
||||
|
||||
|
||||
def _tape_options(source):
|
||||
"""What goes ON each drawer label - shared with tape printing, so a
|
||||
label looks the same whichever printer it comes off."""
|
||||
options = tape_printer.options_from_form(
|
||||
source,
|
||||
size_default=SiteSetting.get('tape_size_mode',
|
||||
tape_printer.DEFAULT_SIZE_MODE))
|
||||
# 'Fit the tape width' is meaningless on die-cut stock: there is no
|
||||
# tape to fit. Fall back to the house sizes.
|
||||
if not options.is_fixed:
|
||||
options.size_mode = tape_printer.DEFAULT_SIZE_MODE
|
||||
return options
|
||||
|
||||
|
||||
def _query(sheet, options, labels, group_spec=None):
|
||||
"""Every option as query args, so preview images and the PDF link show
|
||||
exactly what the page shows."""
|
||||
query = {
|
||||
'ids': ','.join(str(l.id) for l in labels),
|
||||
tape_printer.FORM_MARKER: '1',
|
||||
'stock': sheet.stock,
|
||||
'layout': sheet.layout,
|
||||
'margin_mm': sheet.margin_mm,
|
||||
'gap_mm': sheet.gap_mm,
|
||||
'start_at': sheet.start_at,
|
||||
'size_mode': options.size_mode,
|
||||
'copies': options.copies,
|
||||
'font_scale': options.font_scale,
|
||||
'image_percent': options.image_percent,
|
||||
'padding_mm': options.padding_mm,
|
||||
'align': options.align,
|
||||
'fields': options.fields,
|
||||
}
|
||||
for name, on in (('cut_guides', sheet.cut_guides),
|
||||
('centre', sheet.centre),
|
||||
('monochrome', sheet.monochrome),
|
||||
('group_drawers', options.group_drawers),
|
||||
('outline', options.outline)):
|
||||
if on:
|
||||
query[name] = '1'
|
||||
if group_spec:
|
||||
query['groups'] = group_spec
|
||||
return query
|
||||
|
||||
|
||||
@dymo_bp.route('/', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def index():
|
||||
labels = _selected_labels()
|
||||
if not labels:
|
||||
flash('Select some labels to print first.', 'warning')
|
||||
return redirect(url_for('labels.index'))
|
||||
|
||||
source = request.form if request.method == 'POST' else request.args
|
||||
options = _tape_options(source)
|
||||
sheet = dymo_sheet.sheet_options_from_form(
|
||||
source, stock_default=SiteSetting.get('dymo_stock',
|
||||
dymo_sheet.DEFAULT_STOCK))
|
||||
|
||||
group_spec = _group_spec()
|
||||
plan = tape_printer.plan_groups(labels, options, group_spec)
|
||||
pages = dymo_sheet.plan_sheets(plan, sheet, copies=options.copies)
|
||||
per_sheet, cols, rows = dymo_sheet.capacity(sheet)
|
||||
|
||||
return render_template(
|
||||
'admin/labels/dymo.html',
|
||||
labels=labels,
|
||||
plan=plan,
|
||||
pages=pages,
|
||||
ids=','.join(str(l.id) for l in labels),
|
||||
options=options,
|
||||
sheet=sheet,
|
||||
stocks=dymo_sheet.STOCKS,
|
||||
stock=dymo_sheet.get_stock(sheet.stock),
|
||||
layouts=dymo_sheet.LAYOUTS,
|
||||
size_modes=[(key, text) for key, text in tape_printer.SIZE_MODES
|
||||
if key in tape_printer.FIXED_MODES],
|
||||
per_sheet=per_sheet,
|
||||
grid=f'{cols} across, {rows} down',
|
||||
query=_query(sheet, options, labels, group_spec),
|
||||
field_options=[
|
||||
('image', 'Part image'), ('name', 'Part name'),
|
||||
('dimensions', 'Dimensions'), ('part_number', 'Part number'),
|
||||
('alt_numbers', 'Alternate numbers'), ('category', 'Category'),
|
||||
('category_code', 'Category code'), ('location', 'Drawer'),
|
||||
('colour', 'Colour name'), ('accent', 'Colour stripe'),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _rendered_pages():
|
||||
"""The sticker images for the current request's options."""
|
||||
labels = _selected_labels()
|
||||
if not labels:
|
||||
abort(400, 'No labels selected.')
|
||||
options = _tape_options(request.args)
|
||||
sheet = dymo_sheet.sheet_options_from_form(request.args)
|
||||
plan = tape_printer.plan_groups(labels, options, _group_spec())
|
||||
return dymo_sheet.render_pages(
|
||||
plan, sheet, options, current_app.config['UPLOAD_FOLDER'],
|
||||
copies=options.copies)
|
||||
|
||||
|
||||
@dymo_bp.route('/preview.png')
|
||||
@login_required
|
||||
@admin_required
|
||||
def preview():
|
||||
"""One sticker, scaled for the screen."""
|
||||
pages = _rendered_pages()
|
||||
index_ = request.args.get('page', type=int) or 1
|
||||
if index_ < 1 or index_ > len(pages):
|
||||
abort(404)
|
||||
return send_file(dymo_sheet.preview_png(pages[index_ - 1]),
|
||||
mimetype='image/png')
|
||||
|
||||
|
||||
@dymo_bp.route('/download.pdf', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def download_pdf():
|
||||
"""The print-ready PDF: one page per sticker, exactly the stock size."""
|
||||
labels = _selected_labels()
|
||||
if not labels:
|
||||
flash('Nothing selected.', 'warning')
|
||||
return redirect(url_for('labels.index'))
|
||||
|
||||
source = request.form if request.method == 'POST' else request.args
|
||||
options = _tape_options(source)
|
||||
sheet = dymo_sheet.sheet_options_from_form(source)
|
||||
plan = tape_printer.plan_groups(labels, options, _group_spec())
|
||||
|
||||
buffer = dymo_sheet.render_pdf(
|
||||
plan, sheet, options, current_app.config['UPLOAD_FOLDER'],
|
||||
copies=options.copies)
|
||||
if buffer is None:
|
||||
flash('Nothing to render.', 'warning')
|
||||
return redirect(url_for('labels.index'))
|
||||
|
||||
stock = dymo_sheet.get_stock(sheet.stock)
|
||||
# Truncated, not rounded: 101.6mm stock is sold as "101 x 54".
|
||||
name = f'lego-labels-{int(stock.width_mm)}x{int(stock.height_mm)}mm.pdf'
|
||||
return send_file(buffer, mimetype='application/pdf', as_attachment=True,
|
||||
download_name=name)
|
||||
Reference in new issue
Block a user