Files
odoo-addons/addons/at_master_order/IMAGE_PASTE_FEATURE.md

9.2 KiB

Excel Image Paste Feature for Master Order Lines

Overview

This feature enables users to copy images from Excel and paste them directly into Master Order line image cells using Ctrl+V, providing an Excel-like workflow for product image management.


Feature Summary

Aspect Details
Target Users Purchasing and sourcing teams
Use Case Paste product images from Excel directly into order lines
Supported Formats PNG, JPEG, BMP, GIF, WEBP
Max Image Size Auto-resizes if > 1024x1024 pixels
Storage Odoo filestore via ir.attachment
Browser Support Chrome, Edge, Firefox (desktop)

User Flow

1. Open Master Order form
2. Click on image cell in Order Lines (cell gets focus ring)
3. Copy image from Excel (Ctrl+C on image)
4. Click target image cell
5. Press Ctrl+V
6. Image appears instantly with success notification
7. Save the form to persist

Technical Architecture

Files Structure

at_master_order/
├── static/src/
│   ├── js/
│   │   ├── image_paste_widget.js      # Main OWL widget
│   │   └── master_order_form.js       # Form controller enhancements
│   ├── xml/
│   │   └── image_paste_templates.xml  # OWL templates
│   └── scss/
│       └── image_paste.scss           # Styling for paste interaction
├── models/
│   └── master_order_line.py           # Backend image processing
└── views/
    └── master_order_views.xml         # View with widget="image_paste"

Component Diagram

┌─────────────────────────────────────────────────────────────┐
│                    Browser (Frontend)                        │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────┐    ┌─────────────────────────────┐ │
│  │  ImagePasteField    │    │  Clipboard API              │ │
│  │  (OWL Widget)       │◄───│  (DataTransfer/Clipboard)   │ │
│  │                     │    └─────────────────────────────┘ │
│  │  - onPaste()        │                                    │
│  │  - _processImage()  │    ┌─────────────────────────────┐ │
│  │  - _saveToRecord()  │───►│  Canvas API                 │ │
│  └─────────────────────┘    │  (Image resize)             │ │
│           │                 └─────────────────────────────┘ │
│           ▼                                                 │
│  ┌─────────────────────┐                                    │
│  │  record.update()    │                                    │
│  │  (OWL Record)       │                                    │
│  └─────────────────────┘                                    │
└─────────────────────────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────┐
│                    Server (Backend)                          │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────┐    ┌─────────────────────────────┐ │
│  │  master_order_line  │───►│  ir.attachment              │ │
│  │  - image_1920 field │    │  (Image storage)            │ │
│  │  - write() override │    └─────────────────────────────┘ │
│  │  - _process_image() │                                    │
│  └─────────────────────┘    ┌─────────────────────────────┐ │
│                             │  Filestore                   │ │
│                             │  (Binary data)               │ │
│                             └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Key Implementation Details

1. OWL Widget (image_paste_widget.js)

// Extends Odoo's ImageField
export class ImagePasteField extends ImageField {
    static template = "at_master_order.ImagePasteField";
    
    setup() {
        super.setup();
        // CRITICAL: Extend state, don't replace it!
        this.state.isFocused = false;
        this.state.isProcessing = false;
    }
    
    async onPaste(ev) {
        // 1. Get image from clipboard
        // 2. Process/resize if needed
        // 3. Save via record.update()
    }
}

2. Template (image_paste_templates.xml)

<t t-name="at_master_order.ImagePasteField">
    <div t-ref="imagePasteContainer" tabindex="0">
        <!-- Uses parent's getUrl() method for image display -->
        <img t-att-src="this.getUrl(props.previewImage || props.name)"/>
    </div>
</t>

3. View Configuration (master_order_views.xml)

<field name="image_1920" widget="image_paste" 
       options="{'size': [50, 50]}" 
       optional="show"
       help="Click to focus, then Ctrl+V to paste image from Excel"/>

4. Model Field (master_order_line.py)

image_1920 = fields.Image('Product Image', attachment=True,
    help='Upload product image for this line. Supports paste from Excel (Ctrl+V).')

Critical Lessons Learned

1. OWL State Inheritance

Problem: Overwriting this.state in child widget destroyed parent's isValid property.

// WRONG - destroys parent state
this.state = useState({ isFocused: false });

// CORRECT - extends parent state
this.state.isFocused = false;

2. ImageField URL Generation

The getUrl() method checks !this.state.isValid and returns placeholder if falsy. Parent's state must be preserved.

3. Attachment Storage

Image fields with attachment=True store data in ir_attachment table, not as a database column. Use ORM methods to access, not direct SQL.

4. Cache Invalidation

ImageField uses write_date for cache busting. Ensure fieldDependencies includes write_date.


Database Schema

ir_attachment (Image Storage)

Column Value
res_model at.master.order.line
res_field image_1920
res_id Line ID
type binary
store_fname Filestore path
mimetype image/jpeg, image/png, etc.

Query to Check Images

SELECT l.id, l.name, a.id as attachment_id, a.file_size 
FROM at_master_order_line l 
LEFT JOIN ir_attachment a 
  ON a.res_model = 'at.master.order.line' 
  AND a.res_field = 'image_1920' 
  AND a.res_id = l.id;

Image URL Format

/web/image/at.master.order.line/{line_id}/image_1920?unique={write_date}

Troubleshooting

Issue Cause Solution
Placeholder shown instead of image state.isValid is undefined Don't overwrite parent's this.state
Image not saving Clipboard API not returning data Check browser console, ensure HTTPS
404 on image URL Attachment not linked correctly Check ir_attachment table
No paste event firing Cell not focused Click cell first to focus

Testing Checklist

  • Paste PNG image from Excel
  • Paste JPEG image from Excel
  • Paste large image (> 1024px) - should auto-resize
  • Save form after paste
  • Reload page - image should persist
  • Delete image (Delete key when focused)
  • Multiple pastes on different rows
  • Paste on new unsaved line

Dependencies

Frontend

  • @web/views/fields/image/image_field (ImageField)
  • @web/core/utils/hooks (useService)
  • @odoo/owl (Component, useState, onMounted, useRef)

Backend

  • PIL/Pillow (Image processing)
  • base64 (Encoding)

Version History

Version Date Changes
1.0 2026-01-12 Initial implementation
1.1 2026-01-12 Fixed state inheritance bug
1.2 2026-01-12 Fixed template to use getUrl()