Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions report_positioned_image/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ The module supports two types of images:
- *Report-specific Images*: Configure specific images for individual
reports, filtered by company context and always shown when configured

Images can be assigned to a specific company or left as shared records
(without company assignment) for use across multiple companies

**Table of contents**

.. contents::
Expand Down Expand Up @@ -68,6 +71,8 @@ To configure company-level images:
height automatically adjusts the other dimension to maintain
aspect ratio. Uncheck for manual control of both dimensions.
- *First Page Only*: Check to show only on the first page
- *Company*: Automatically set to the current company when creating
from the company form. To create shared images, leave empty.

To configure report-specific images:

Expand Down
89 changes: 44 additions & 45 deletions report_positioned_image/models/ir_actions_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,42 +22,6 @@ class IrActionsReport(models.Model):
string="Report Images",
)

def _render_qweb_pdf(self, report_ref, res_ids=None, data=None):
"""Set company context so _get_positioned_image_configs uses the
correct company.
"""
company = self._get_report_company(res_ids)
return super(IrActionsReport, self.with_company(company))._render_qweb_pdf(
report_ref, res_ids, data
)

def _prepare_html(self, html, report_model=False):
image_configs = self._get_positioned_image_configs()
if not image_configs:
return super()._prepare_html(html, report_model=report_model)
result = super()._prepare_html(html, report_model=report_model)
if not isinstance(result, tuple):
return result
bodies, res_ids, header, footer, specific_paperformat_args = result
if image_configs:
header = self._inject_images_into_header(header, image_configs)
return bodies, res_ids, header, footer, specific_paperformat_args

def _inject_images_into_header(self, header, image_configs):
image_html = self._build_image_html(image_configs)
return self._insert_html_into_header(header, image_html)

def _insert_html_into_header(self, header, html_to_inject):
if Markup("</body>") in header:
return header.replace(
Markup("</body>"), html_to_inject + Markup("</body>"), 1
)
if Markup("<body>") in header:
return header.replace(
Markup("<body>"), Markup("<body>") + html_to_inject, 1
)
return header + html_to_inject

@staticmethod
def _build_image_html(images):
parts = []
Expand Down Expand Up @@ -85,15 +49,20 @@ def _build_image_html(images):
)
return Markup("".join(parts))

def _get_report_company(self, res_ids):
if not res_ids or not self.model:
return self.env.company
model = self.env[self.model]
if "company_id" not in model._fields:
return self.env.company
records = model.browse(res_ids).exists()
companies = records.mapped("company_id")
return companies[0] if len(companies) == 1 else self.env.company
def _insert_html_into_header(self, header, html_to_inject):
if Markup("</body>") in header:
return header.replace(
Markup("</body>"), html_to_inject + Markup("</body>"), 1
)
if Markup("<body>") in header:
return header.replace(
Markup("<body>"), Markup("<body>") + html_to_inject, 1
)
return header + html_to_inject

def _inject_images_into_header(self, header, image_configs):
image_html = self._build_image_html(image_configs)
return self._insert_html_into_header(header, image_html)

def _get_positioned_image_configs(self):
company = self.env.company
Expand All @@ -114,3 +83,33 @@ def _get_positioned_image_configs(self):
for img in images
if img.image
]

def _prepare_html(self, html, report_model=False):
image_configs = self._get_positioned_image_configs()
if not image_configs:
return super()._prepare_html(html, report_model=report_model)
result = super()._prepare_html(html, report_model=report_model)
if not isinstance(result, tuple):
return result
bodies, res_ids, header, footer, specific_paperformat_args = result
header = self._inject_images_into_header(header, image_configs)
return bodies, res_ids, header, footer, specific_paperformat_args

def _get_report_company(self, res_ids):
if not res_ids or not self.model:
return self.env.company
model = self.env[self.model]
if "company_id" not in model._fields:
return self.env.company
records = model.browse(res_ids).exists()
companies = records.mapped("company_id")
return companies[0] if len(companies) == 1 else self.env.company

def _render_qweb_pdf(self, report_ref, res_ids=None, data=None):
"""Set company context so _get_positioned_image_configs uses the
correct company.
"""
company = self._get_report_company(res_ids)
return super(IrActionsReport, self.with_company(company))._render_qweb_pdf(
report_ref, res_ids, data
)
43 changes: 31 additions & 12 deletions report_positioned_image/models/report_positioned_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ class ReportPositionedImage(models.Model):
def _default_company_id(self):
return self.env.context.get("default_company_id")

@api.constrains("pos_top", "pos_left", "width", "height")
def _check_positive_values(self):
"""Ensure position and dimension fields have positive values."""
for record in self:
if record.pos_top < 0:
raise ValidationError(_("Top position must be a positive value."))
if record.pos_left < 0:
raise ValidationError(_("Left position must be a positive value."))
if record.width <= 0:
raise ValidationError(_("Width must be greater than zero."))
if record.height <= 0:
raise ValidationError(_("Height must be greater than zero."))

def _get_aspect_ratio(self):
"""Get image aspect ratio (width/height)."""
if not self.image:
Expand Down Expand Up @@ -83,15 +96,21 @@ def _onchange_height(self):
self.height * ratio, 2
)

@api.constrains("pos_top", "pos_left", "width", "height")
def _check_positive_values(self):
"""Ensure position and dimension fields have positive values."""
for record in self:
if record.pos_top < 0:
raise ValidationError(_("Top position must be a positive value."))
if record.pos_left < 0:
raise ValidationError(_("Left position must be a positive value."))
if record.width <= 0:
raise ValidationError(_("Width must be greater than zero."))
if record.height <= 0:
raise ValidationError(_("Height must be greater than zero."))
@api.onchange("company_id")
def _onchange_company_id(self):
"""Prevent assigning to a different company when created from company form."""
default_company_id = self.env.context.get("default_company_id")
if not default_company_id:
return
if self.company_id and self.company_id.id != default_company_id:
self.company_id = default_company_id
return {
"warning": {
"title": _("Company Assignment"),
"message": _(
"You cannot assign this image to a different company. "
"Please use the dedicated wizard to assign images to other "
"companies."
),
}
}
2 changes: 2 additions & 0 deletions report_positioned_image/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ To configure company-level images:
automatically adjusts the other dimension to maintain aspect ratio.
Uncheck for manual control of both dimensions.
- *First Page Only*: Check to show only on the first page
- *Company*: Automatically set to the current company when creating from
the company form. To create shared images, leave empty.

To configure report-specific images:

Expand Down
3 changes: 3 additions & 0 deletions report_positioned_image/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ The module supports two types of images:
included in reports by enabling the *Include Company Images* option
- *Report-specific Images*: Configure specific images for individual reports,
filtered by company context and always shown when configured

Images can be assigned to a specific company or left as shared records
(without company assignment) for use across multiple companies
4 changes: 4 additions & 0 deletions report_positioned_image/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ <h1 class="title">Report Positioned Image</h1>
<li><em>Report-specific Images</em>: Configure specific images for individual
reports, filtered by company context and always shown when configured</li>
</ul>
<p>Images can be assigned to a specific company or left as shared records
(without company assignment) for use across multiple companies</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
Expand Down Expand Up @@ -415,6 +417,8 @@ <h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
height automatically adjusts the other dimension to maintain
aspect ratio. Uncheck for manual control of both dimensions.</li>
<li><em>First Page Only</em>: Check to show only on the first page</li>
<li><em>Company</em>: Automatically set to the current company when creating
from the company form. To create shared images, leave empty.</li>
</ul>
</li>
</ol>
Expand Down
40 changes: 40 additions & 0 deletions report_positioned_image/tests/test_report_positioned_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,43 @@ def test_global_images_appear_for_all_companies(self):
self.company_b
)._get_positioned_image_configs()
self.assertEqual(len(configs_b), 1)

def test_company_id_onchange_with_context(self):
image = (
self.env["report.positioned.image"]
.with_context(default_company_id=self.company_a.id)
.new(
{
"name": "Test Image",
"image": self.test_image,
"width": 10.0,
"height": 10.0,
"company_id": self.company_a.id,
}
)
)
image.company_id = self.company_b
result = image._onchange_company_id()
self.assertIsNotNone(result)
self.assertIn("warning", result)
self.assertEqual(image.company_id, self.company_a)
image.company_id = self.company_a
result = image._onchange_company_id()
self.assertIsNone(result)
self.assertEqual(image.company_id, self.company_a)
image.company_id = False
result = image._onchange_company_id()
self.assertIsNone(result)
self.assertFalse(image.company_id)
image_no_context = self.env["report.positioned.image"].new(
{
"name": "Free Image",
"image": self.test_image,
"width": 10.0,
"height": 10.0,
"company_id": self.company_b.id,
}
)
result = image_no_context._onchange_company_id()
self.assertIsNone(result)
self.assertEqual(image_no_context.company_id, self.company_b)
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
<group>
<group>
<field name="name" />
<field name="company_id" />
<field
name="company_id"
groups="base.group_multi_company"
/>
</group>
<group>
<field name="pos_top" />
Expand All @@ -31,7 +34,7 @@
<field name="arch" type="xml">
<list>
<field name="name" />
<field name="company_id" />
<field name="company_id" groups="base.group_multi_company" />
<field name="first_page_only" />
</list>
</field>
Expand Down
8 changes: 2 additions & 6 deletions report_positioned_image/views/res_company_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
name="report_positioned_image_ids"
nolabel="1"
context="{'default_company_id': id}"
domain="[('company_id', '=', id)]"
domain="['|', ('company_id', '=', id), ('company_id', '=', False)]"
>
<list editable="bottom">
<field name="name" />
Expand All @@ -26,11 +26,7 @@
<field name="height" string="Height (mm)" />
<field name="respect_image_ratio" />
<field name="first_page_only" />
<field
name="company_id"
column_invisible="1"
required="1"
/>
<field name="company_id" column_invisible="1" />
</list>
</field>
</page>
Expand Down
Loading