تطوير أودو

كيفية تعيين صورة افتراضية لحقل ثنائي في أودو (16–19)

مرجع شامل للمطوّر لتعيين الصور الافتراضية على حقول أودو الثنائية وحقول الصور: fields.Image مقابل fields.Binary وأسلوب file_open + base64 وطرق _default_image وأصول الوحدة الثابتة وخاصية placeholder لعنصر واجهة الصورة. موثق لأودو 18 و19.

iWesabe Editorial Team١ أبريل ٢٠٢٢8 دقائق للقراءة

تعيين صورة افتراضية على حقل ثنائي أو حقل صورة في أودو متطلب شائع: صورة رمزية افتراضية لجهات الاتصال الجديدة، أو صورة منتج عنصر نائب، أو شعار الشركة مُعبأ مسبقًا في سجل جديد. تغيّر نهج التنفيذ بشكل كبير بين أودو 14 وأودو 16 مع إدخال `fields.Image` كنوع حقل صورة مخصص. يغطي هذا الدليل كلًا من نهج `fields.Image` الحديث (أودو 16+) ونهج `fields.Binary` القديم، حتى تتمكن من التعامل مع الوحدات الجديدة والكود القديم على حدٍّ سواء.

fields.Image مقابل fields.Binary — أيهما تستخدم

مقارنة أنواع حقول تخزين الصور في أودو
الخاصيةfields.Imagefields.Binary
متاح منذأودو 13 (مستقر، موصى به من أودو 14+)جميع إصدارات أودو
التخزينبايتات مُرمَّزة بـ base64 في قاعدة البيانات (مثل Binary)بايتات مُرمَّزة بـ base64 في قاعدة البيانات
تغيير الحجم التلقائينعم — max_width وmax_height يُغيّران الحجم تلقائيًا عند الحفظلا — يُخزّن أي بايتات تُمرّرها
صورة مصغرة تلقائيةنعم — يُنشئ أودو حقول shadow لـ image_128 وimage_256 تلقائيًالا — حقول الصورة المصغرة يدوية
الأفضل لـأي صورة (صورة رمزية، صورة منتج، شعار شركة). استخدم هذا في جميع التطوير الجديد.الملفات الثنائية غير الصورية (مرفقات PDF، تصدير Excel). يُستخدم أيضًا في وحدات أودو 12 والإصدارات الأقدم.

تخزين الصور الافتراضية في وحدتك

يجب أن يشحن ملف الصورة الافتراضية داخل مجلد وحدتك. الموقع القياسي هو `static/img/` — هذا يعكس النمط المستخدم في وحدات أودو الخاصة (مثلًا `mail/static/img/default_image.png`). يُقرأ الملف عند تثبيت الوحدة (أو عند استدعاء الدالة الافتراضية لسجل جديد) ويتم تحويله إلى سلسلة base64 مُخزَّنة في قاعدة البيانات.

text
my_module/
├── __manifest__.py
├── __init__.py
├── models/
│   └── my_model.py
├── static/
│   └── img/
│       └── default_avatar.png   ← default image file goes here

تعيين صورة افتراضية على fields.Image (الحديث — أودو 14+)

يستخدم النمط الموصى به دالة على مستوى الوحدة لقراءة ملف الصورة وإعادته كقيمة بايتات مُرمَّزة بـ base64. ثم تُمرَّر هذه الدالة إلى معامل `default=` لـ `fields.Image`.

python
# models/my_model.py
import base64
from odoo import fields, models
from odoo.modules.module import get_module_resource


def _default_image():
    """Read the bundled default avatar and return it as base64 bytes."""
    img_path = get_module_resource("my_module", "static", "img", "default_avatar.png")
    with open(img_path, "rb") as f:
        return base64.b64encode(f.read())


class MyModel(models.Model):
    _name = "my.model"
    _description = "My Model"

    name = fields.Char(required=True)

    image_1920 = fields.Image(
        string="Image",
        max_width=1920,
        max_height=1920,
        default=_default_image,   # ← pass the function object, not the call
    )
    # Odoo auto-generates image_128 and image_256 shadow fields from image_1920

تعيين صورة افتراضية على fields.Binary (النمط القديم)

في وحدات أودو 12 و13 (أو الكود القديم الذي تصونه)، ينطبق نفس النمط باستخدام `fields.Binary`. الفرق الوحيد هو إعلان الحقل — دالة `_default_image` واستدعاء `get_module_resource` متطابقان.

python
# models/my_model.py  (Odoo 12/13 legacy pattern)
import base64
from odoo import fields, models
from odoo.modules.module import get_module_resource


def _default_image():
    img_path = get_module_resource("my_module", "static", "img", "default_avatar.png")
    with open(img_path, "rb") as f:
        return base64.b64encode(f.read())


class MyModel(models.Model):
    _name = "my.model"
    _description = "My Model"

    name = fields.Char(required=True)

    image = fields.Binary(
        string="Image",
        attachment=True,   # store in ir.attachment instead of database column
        default=_default_image,
    )
    image_medium = fields.Binary(
        string="Medium Image",
        attachment=True,
    )
    image_small = fields.Binary(
        string="Small Image",
        attachment=True,
    )
    # In legacy Odoo 12/13, you had to generate resized variants yourself.
    # In Odoo 14+ with fields.Image, this is automatic.

بديل: استخدام file_open بدلًا من get_module_resource

بديل أقصر قليلًا هو `odoo.tools.misc.file_open`، الذي يقبل مسارًا نسبيًا للوحدة ويتعامل مع تخطيطات الوحدة في التطوير والإنتاج (المُجمَّعة بـ zip). كلا النهجين يعمل — اختر الذي يطابق الأسلوب الموجود في قاعدة الكود الخاصة بك.

python
# Alternative using file_open
import base64
from odoo import fields, models
from odoo.tools.misc import file_open


def _default_image():
    # file_open path: <module_name>/<relative_path_within_module>
    with file_open("my_module/static/img/default_avatar.png", "rb") as f:
        return base64.b64encode(f.read())


class MyModel(models.Model):
    _name = "my.model"
    _description = "My Model"

    image_1920 = fields.Image(
        string="Image",
        max_width=1920,
        max_height=1920,
        default=_default_image,
    )

خاصية placeholder — إظهار صورة افتراضية في العرض بدون تخزينها

أحيانًا تريد عنصرًا نائبًا مرئيًا في عرض النموذج عندما لا تكون هناك صورة محددة، دون ملء حقل قاعدة البيانات مسبقًا لكل سجل جديد. تفعل خاصية `placeholder` على عنصر `` ذلك بالضبط — تعرض صورة احتياطية في واجهة المستخدم فقط، ولا تكتب أي شيء في قاعدة البيانات.

xml
<!-- views/my_model_views.xml -->
<record id="view_my_model_form" model="ir.ui.view">
    <field name="name">my.model.form</field>
    <field name="model">my.model</field>
    <field name="arch" type="xml">
        <form string="My Model">
            <sheet>
                <field name="image_1920"
                       widget="image"
                       class="oe_avatar"
                       placeholder="/my_module/static/img/default_avatar.png"/>
                <group>
                    <field name="name"/>
                </group>
            </sheet>
        </form>
    </field>
</record>

ملء الصورة الافتراضية بأثر رجعي على السجلات الحالية

عند إضافة `default=_default_image` إلى حقل موجود في وحدة نشرت بالفعل، ينطبق الافتراضي فقط على السجلات الجديدة التي تُنشأ بعد تحديث الوحدة. ستظل السجلات الحالية التي لم يكن لها صورة قبل التحديث بدون صورة. لملء السجلات الحالية بأثر رجعي، أضف `post_init_hook` أو نص ترحيل.

python
# __manifest__.py
{
    "name": "My Module",
    "version": "17.0.1.1.0",
    "post_init_hook": "post_init_hook",   # ← add this
    ...
}

# __init__.py
from .hooks import post_init_hook   # ← add this

# hooks.py  (new file)
import base64
from odoo.modules.module import get_module_resource


def post_init_hook(env):
    """Back-fill default image on records that have none."""
    img_path = get_module_resource("my_module", "static", "img", "default_avatar.png")
    with open(img_path, "rb") as f:
        default_image = base64.b64encode(f.read())

    records_without_image = env["my.model"].search([("image_1920", "=", False)])
    records_without_image.write({"image_1920": default_image})

استكشاف الأخطاء وإصلاحها

المشكلات الشائعة عند تعيين الصور الافتراضية على حقول أودو الثنائية/الصور
العَرَضالسببالحل
تُنشأ السجلات الجديدة بدون الصورة الافتراضيةاستُدعيت الدالة الافتراضية عند تعريف الفئة بدلًا من تمريرها كقابل للاستدعاء — مثلًا default=_default_image() بدلًا من default=_default_imageاحذف الأقواس: default=_default_image (مرّر كائن الدالة، وليس قيمة إعادتها).
FileNotFoundError أو get_module_resource تُعيد Noneمسار ملف الصورة خاطئ — الملف غير موجود في static/img/default_avatar.png نسبةً إلى جذر الوحدةتحقق من وجود الملف: ls <addons_path>/my_module/static/img/. تحقق من الأخطاء الإملائية في اسم الملف. تُعيد get_module_resource القيمة None (وليس خطأ) إذا لم يُعثر على الملف — أضف فحصًا: assert img_path, 'Default image not found'.
تظهر الصورة الافتراضية للسجلات الجديدة في التطوير لكن ليس في الإنتاجلم تُدرج الملفات الثابتة للوحدة في النشر (مفقودة من commit git أو بناء أودو.sh)تأكد أن static/img/default_avatar.png مُثبَّت في المستودع (git status). على أودو.sh، تحقق من سجل البناء — يجب أن يكون الملف في شجرة المستودع، وليس مُدرجًا في .gitignore.
يُخزّن حقل الصورة سلسلة base64 كنص بدلًا من عرض الصورةالحقل fields.Binary بدون widget='image' في XML العرض، أو الصورة مُخزَّنة عن طريق الخطأ كـ Python str بدلًا من bytes1) أضف widget='image' إلى عنصر <field> في العرض. 2) في الدالة الافتراضية، تأكد أن base64.b64encode() تُعيد bytes. لا تفكك إلى str — يتوقع fields.Image وfields.Binary قيمة bytes.
حقول shadow image_128/image_256 فارغة حتى لو كانت image_1920 لها قيمةكُتبت الصورة مباشرةً إلى قاعدة البيانات (مثلًا عبر سكريبت SQL أو كتابة ORM منخفضة المستوى تجاوزت منطق حساب الحقل) دون تشغيل منطق تغيير حجم fields.Imageأعد كتابة الحقل عبر ORM: records.write({'image_1920': the_image_bytes}). تُشغّل كتابة ORM تغيير الحجم وملء حقول shadow. عمليات الإدراج المباشرة عبر SQL تتجاوز هذا.

ملاحظات الإصدار

ملاحظات إصدار أودو للصورة الافتراضية على الحقول الثنائية/الصور
إصدار أودوالتغييرات الرئيسية المؤثرة على الصورة الافتراضية على الحقول الثنائية/الصور
أودو 14 و15استقر fields.Image وأصبح موصى به لجميع حقول الصور الجديدة. لا يزال نمط _default_image في fields.Binary يعمل لكنه يُعدّ قديمًا. حقول shadow image_128 وimage_256 تُنشأ تلقائيًا بواسطة fields.Image. get_module_resource متاحة في odoo.modules.module.
أودو 16نُقلت file_open من odoo.tools إلى odoo.tools.misc — حدّث الاستيرادات إذا كنت تهاجر من أودو 15. fields.Image ونمط default= لم يتغيرا. أصبحت اتفاقية التسمية image_1920/image_128/image_256 للصور الرمزية/صور جهات الاتصال معيارًا عبر قاعدة كود أودو.
أودو 17لا توجد تغييرات كاسرة على fields.Image أو نمط default=. post_init_hook ونصوص الترحيل للملء بأثر رجعي لم تتغير. عنصر واجهة الصورة في عروض النماذج يقبل placeholder كما كان من قبل.
أودو 18 و19لا توجد تغييرات كاسرة. fields.Image مع default=_default_image يعمل بشكل مطابق. مسارات get_module_resource وfile_open لم تتغير. تغيير الحجم التلقائي max_width/max_height وإنشاء حقول shadow لم يتغيرا.

تحتاج تطويرًا مخصصًا لأودو لأعمالك؟

يبني فريق أودو المعتمد لدينا وحدات مخصصة ويُهاجر قواعد الكود القديمة إلى أودو 17/18 ويُقدّم تنفيذات كاملة في جميع أنحاء المملكة العربية السعودية — بما في ذلك تكامل ZATCA المرحلة الثانية وواجهة المستخدم العربية وGOSI وامتثال WPS-Mudad.

واتساب

الأسئلة الشائعة

كيف أعيّن صورة افتراضية لحقل fields.Image في أودو؟
عرّف دالة على مستوى الوحدة تقرأ ملف صورتك وتُعيده كبايتات مُرمَّزة بـ base64، ثم مرّر تلك الدالة إلى معامل `default=` لـ `fields.Image`. استخدم `get_module_resource('my_module', 'static', 'img', 'default.png')` للحصول على مسار الملف، وافتحه في الوضع الثنائي، وأعد `base64.b64encode(f.read())`. خزّن ملف الصورة في `my_module/static/img/default.png`. مرّر دائمًا كائن الدالة (`default=_default_image`)، وليس نتيجة استدعائها (`default=_default_image()`).
ما الفرق بين fields.Image وfields.Binary لتخزين الصور في أودو؟
يُعيد `fields.Image` (تم إدخاله في أودو 13، موصى به من أودو 14+) تغيير حجم الصور باستخدام `max_width`/`max_height` وينشئ تلقائيًا حقول shadow للصور المصغرة `image_128` و`image_256` — مطابقًا لنمط أودو القياسي المستخدم في res.partner وproduct.template. يُخزّن `fields.Binary` أي محتوى ثنائي دون تغيير الحجم أو إنشاء صور مصغرة. لجميع حقول الصور الجديدة في أودو 14+، استخدم `fields.Image`. استخدم `fields.Binary` فقط للملفات الثنائية غير الصورية (ملفات PDF والجداول الإلكترونية وما إلى ذلك).
أين يجب أن أُخزّن ملف الصورة الافتراضية داخل وحدة أودو الخاصة بي؟
خزّن الصورة الافتراضية في `my_module/static/img/default_image.png` (أو `.jpg`). مجلد `static/` في وحدة أودو هو الموقع القياسي للأصول الثابتة — يُخدَّم مباشرةً بواسطة خادم الويب وهو متاح في وقت التشغيل. المجلد الفرعي `img/` هو الاتفاقية لأصول الصور. سيجد كلٌّ من `get_module_resource` و`file_open` الملفات هنا بشكل صحيح في بيئتي التطوير والإنتاج (أودو.sh).
كيف أضيف صورة عنصر نائب في عرض نموذج أودو دون تخزين صورة افتراضية في قاعدة البيانات؟
استخدم خاصية `placeholder` على عنصر `` في XML عرض النموذج: ``. مسار placeholder هو مسار URL نسبي لعنوان URL الأساسي لأودو — يُخدَّم من مجلد الوحدة الثابت. يظهر placeholder فقط في واجهة المستخدم عندما يكون الحقل فارغًا؛ لا يكتب أي شيء في قاعدة البيانات.
كيف أملأ صورة افتراضية بأثر رجعي على السجلات الحالية بعد إضافة افتراضي لحقل صورة؟
ينطبق معامل `default=` فقط على السجلات الجديدة — لا تتأثر السجلات الحالية بدون صورة بإضافة الافتراضي أو تغييره. لملء السجلات الحالية بأثر رجعي: (1) للتثبيت الجديد، أضف `post_init_hook` في `__manifest__.py` يبحث عن السجلات بدون صورة ويكتب الافتراضي عليها عبر ORM؛ (2) للترقية، استخدم نص ترحيل في `migrations//post-migrate.py` مع دالة `migrate(cr, version)` تُنفّذ نفس الملء بأثر رجعي. استخدم دائمًا كتابات ORM (`records.write({...})`) بدلًا من SQL — تُشغّل كتابات ORM تغيير الحجم وملء حقول shadow.
لماذا تظهر صورتي الافتراضية في التطوير لكن ليس بعد النشر على أودو.sh؟
السبب الأكثر شيوعًا هو عدم تثبيت ملف الصورة في مستودع git. على أودو.sh، تتوفر فقط الملفات الموجودة في المستودع في وقت التشغيل — أي ملف أضفته محليًا لكنك نسيت تثبيته سيكون مفقودًا. شغّل `git status` للتحقق. تأكد أيضًا أن الملف ليس في `.gitignore`. بعد تأكيد تثبيت الملف، ادفع إلى أودو.sh — سيتضمنه البناء التالي. كفحص ثانوي، تأكد أن المسار الممرَّر إلى `get_module_resource` أو `file_open` يطابق مسار الملف الفعلي في المستودع تمامًا (حساس لحالة الأحرف على Linux).
iWesabe Editorial Team

iWesabe Editorial Team

رؤى عملية حول Odoo ERP وامتثال ZATCA والعمليات الرقمية للشركات السعودية — بقلم فرق الاستشارات والمالية والهندسة في iWesabe.

عن iWesabe

مقالات ذات صلة