Initial commit: Odoo 18.0-20251222 extra-addons
This commit is contained in:
3
database_size/report/__init__.py
Executable file
3
database_size/report/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
||||
# Copyright 2025 Opener B.V. <https://opener.amsterdam>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
from . import ir_model_size_report
|
||||
232
database_size/report/ir_model_size_report.py
Executable file
232
database_size/report/ir_model_size_report.py
Executable file
@@ -0,0 +1,232 @@
|
||||
# Copyright 2025 Opener B.V. <https://opener.amsterdam>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class IrModelSizeReport(models.Model):
|
||||
_name = "ir.model.size.report"
|
||||
_description = "Historical Disk space usage per model"
|
||||
_auto = False
|
||||
_rec_name = "historical_measurement_date"
|
||||
_order = "historical_measurement_date desc, diff_total_model_size desc"
|
||||
|
||||
model = fields.Char()
|
||||
model_name = fields.Char()
|
||||
|
||||
measurement_date = fields.Date("Date of Measurement")
|
||||
historical_measurement_date = fields.Date("Historical Date of Measurement")
|
||||
|
||||
total_model_size = fields.Integer()
|
||||
historical_total_model_size = fields.Integer()
|
||||
diff_total_model_size = fields.Integer("Change in Total Model Size")
|
||||
|
||||
total_database_size = fields.Integer()
|
||||
historical_total_database_size = fields.Integer()
|
||||
diff_total_database_size = fields.Integer("Change in Total Database Size")
|
||||
|
||||
total_table_size = fields.Integer()
|
||||
historical_total_table_size = fields.Integer()
|
||||
|
||||
table_size = fields.Integer("Bare Table Size")
|
||||
historical_table_size = fields.Integer("Historical Bare Table Size")
|
||||
|
||||
indexes_size = fields.Integer("Index Size")
|
||||
historical_indexes_size = fields.Integer("Historical Index Size")
|
||||
|
||||
relations_size = fields.Integer("Many2many Tables Size")
|
||||
historical_relations_size = fields.Integer("Historical Many2many Tables Size")
|
||||
|
||||
tuples = fields.Integer("Estimated Rows")
|
||||
historical_tuples = fields.Integer("Historical Estimated Rows")
|
||||
|
||||
attachment_size = fields.Integer()
|
||||
historical_attachment_size = fields.Integer()
|
||||
|
||||
def action_open_model_sizes(self):
|
||||
"""Open the model_sizes from the report line.
|
||||
|
||||
At this point, the 'virtual' report record might not exist anymore
|
||||
so we fetch the dates from the context.
|
||||
"""
|
||||
self.ensure_one()
|
||||
domain = [
|
||||
("model", "=", self.model),
|
||||
(
|
||||
"measurement_date",
|
||||
"in",
|
||||
(
|
||||
self.env.context.get("measurement_date"),
|
||||
self.env.context.get("historical_measurement_date"),
|
||||
),
|
||||
),
|
||||
]
|
||||
action = self.env["ir.actions.actions"]._for_xml_id(
|
||||
"database_size.ir_model_size_action"
|
||||
)
|
||||
action["domain"] = domain
|
||||
return action
|
||||
|
||||
@api.model
|
||||
def _move_dates_to_context(self, domain):
|
||||
"""Move the requested comparison date from the domain into the context.
|
||||
|
||||
The values in the context will be used when creating the virtual table
|
||||
in `_table_query`.
|
||||
"""
|
||||
new_domain = []
|
||||
values = {}
|
||||
for clause in domain or []:
|
||||
for field in ("measurement_date", "historical_measurement_date"):
|
||||
if not isinstance(clause, tuple | list) or clause[0] != field:
|
||||
continue
|
||||
if field in values:
|
||||
raise UserError(
|
||||
self.env._(
|
||||
f"You cannot search on more than one value for {field} "
|
||||
"at the same time."
|
||||
)
|
||||
)
|
||||
if clause[1] in ("=", "==") and clause[2]:
|
||||
values[field] = clause[2]
|
||||
else:
|
||||
raise UserError(
|
||||
self.env._(
|
||||
f"Searching {field} for '{clause[1]} {clause[2]}' is "
|
||||
"not supported."
|
||||
)
|
||||
)
|
||||
new_domain.append((1, "=", 1))
|
||||
else:
|
||||
new_domain.append(clause)
|
||||
if values:
|
||||
self = self.with_context(**values)
|
||||
return self, new_domain
|
||||
|
||||
@api.model
|
||||
def _where_calc(self, domain, active_test=True):
|
||||
"""Move the requested dates from the domain into the context"""
|
||||
(self, new_domain) = self._move_dates_to_context(domain)
|
||||
return super()._where_calc(new_domain, active_test=active_test)
|
||||
|
||||
@api.model
|
||||
def search(self, domain, offset=0, limit=None, order=None):
|
||||
"""Move the requested dates from the domain into the context"""
|
||||
(self, new_domain) = self._move_dates_to_context(domain)
|
||||
return super().search(new_domain, offset=offset, limit=limit, order=order)
|
||||
|
||||
@api.model
|
||||
def search_count(self, domain, limit=None):
|
||||
"""Move the requested dates from the domain into the context"""
|
||||
(self, new_domain) = self._move_dates_to_context(domain)
|
||||
return super().search_count(new_domain, limit=limit)
|
||||
|
||||
@property
|
||||
def _table_query(self):
|
||||
"""Report comparative database size changes between two dates.
|
||||
|
||||
The dates are inserted in the context in this model's `search` method.
|
||||
"""
|
||||
measurement_date = self.env.context.get("measurement_date")
|
||||
if measurement_date:
|
||||
measurement_date = fields.Date.to_date(measurement_date)
|
||||
if not self.env["ir.model.size"].search(
|
||||
[("measurement_date", "=", measurement_date)],
|
||||
limit=1,
|
||||
):
|
||||
raise UserError(
|
||||
self.env._(
|
||||
"There is no data from "
|
||||
f"{fields.Date.to_string(measurement_date)}"
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Use the most recent measurement by default
|
||||
measurement_date = (
|
||||
self.env["ir.model.size"]
|
||||
.search([], order="id desc", limit=1)
|
||||
.measurement_date
|
||||
)
|
||||
if not measurement_date:
|
||||
raise UserError(self.env._("There does not seem to be any data"))
|
||||
|
||||
historical_measurement_date = self.env.context.get(
|
||||
"historical_measurement_date"
|
||||
)
|
||||
if historical_measurement_date:
|
||||
historical_measurement_date = fields.Date.to_date(
|
||||
historical_measurement_date
|
||||
)
|
||||
if not self.env["ir.model.size"].search(
|
||||
[("measurement_date", "=", historical_measurement_date)],
|
||||
limit=1,
|
||||
):
|
||||
raise UserError(
|
||||
self.env._(
|
||||
"There is no data from "
|
||||
f"{fields.Date.to_string(historical_measurement_date)}"
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Use last month by default
|
||||
last_month = measurement_date - relativedelta(months=1)
|
||||
historical_measurement_date = (
|
||||
self.env["ir.model.size"]
|
||||
.search(
|
||||
[
|
||||
("measurement_date", ">=", last_month),
|
||||
("measurement_date", "<", measurement_date),
|
||||
],
|
||||
order="measurement_date asc",
|
||||
limit=1,
|
||||
)
|
||||
.measurement_date
|
||||
)
|
||||
if not historical_measurement_date:
|
||||
raise UserError(
|
||||
self.env._("There does not seem to be enough data to compare")
|
||||
)
|
||||
|
||||
return self.env.cr.mogrify(
|
||||
"""
|
||||
select %(measurement_date)s as measurement_date,
|
||||
%(historical_measurement_date)s as historical_measurement_date,
|
||||
cur.id as id,
|
||||
cur.model,
|
||||
cur.model_name,
|
||||
cur.total_model_size,
|
||||
coalesce(hst.total_model_size, 0) as historical_total_model_size,
|
||||
coalesce(cur.total_model_size) - coalesce(hst.total_model_size, 0)
|
||||
as diff_total_model_size,
|
||||
|
||||
cur.total_database_size,
|
||||
coalesce(hst.total_database_size, 0) as historical_total_database_size,
|
||||
coalesce(cur.total_database_size, 0) - coalesce(hst.total_database_size, 0)
|
||||
as diff_total_database_size,
|
||||
|
||||
cur.table_size,
|
||||
coalesce(hst.table_size, 0) as historical_table_size,
|
||||
cur.total_table_size,
|
||||
coalesce(hst.total_table_size, 0) as historical_total_table_size,
|
||||
cur.indexes_size,
|
||||
coalesce(hst.indexes_size, 0) as historical_indexes_size,
|
||||
cur.relations_size,
|
||||
coalesce(hst.relations_size, 0) as historical_relations_size,
|
||||
cur.tuples,
|
||||
coalesce(hst.tuples, 0) as historical_tuples,
|
||||
cur.attachment_size,
|
||||
coalesce(hst.attachment_size, 0) as historical_attachment_size
|
||||
|
||||
from ir_model_size cur
|
||||
left join ir_model_size hst
|
||||
on cur.model = hst.model
|
||||
and hst.measurement_date = %(historical_measurement_date)s
|
||||
where cur.measurement_date = %(measurement_date)s
|
||||
""",
|
||||
{
|
||||
"measurement_date": measurement_date,
|
||||
"historical_measurement_date": historical_measurement_date,
|
||||
},
|
||||
).decode("utf-8")
|
||||
82
database_size/report/ir_model_size_report_views.xml
Executable file
82
database_size/report/ir_model_size_report_views.xml
Executable file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="ir_model_size_report_view_search" model="ir.ui.view">
|
||||
<field name="model">ir.model.size.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="measurement_date" />
|
||||
<field name="historical_measurement_date" />
|
||||
<field name="model" />
|
||||
<separator />
|
||||
<filter
|
||||
string="One Year Ago"
|
||||
name="filter_one_year_ago"
|
||||
domain="[('historical_measurement_date', '=', (context_today() - relativedelta(years=1)).strftime('%Y-%m-%d'))]"
|
||||
/>
|
||||
<filter
|
||||
string="One Month Ago"
|
||||
name="filter_one_month_ago"
|
||||
domain="[('historical_measurement_date', '=', (context_today() - relativedelta(months=1)).strftime('%Y-%m-%d'))]"
|
||||
/>
|
||||
<separator />
|
||||
<filter
|
||||
string="Notable Change"
|
||||
name="filter_diff_total_model_size"
|
||||
domain="[('diff_total_model_size', '>', 5)]"
|
||||
/>
|
||||
<separator />
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record id="ir_model_size_report_view_tree" model="ir.ui.view">
|
||||
<field name="model">ir.model.size.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<list
|
||||
editable="bottom"
|
||||
create="false"
|
||||
delete="false"
|
||||
edit="false"
|
||||
class="ir_model_size_wrap_header"
|
||||
>
|
||||
<field name="model" optional="hide" />
|
||||
<field name="model_name" />
|
||||
<field name="historical_measurement_date" />
|
||||
<field name="measurement_date" optional="hide" />
|
||||
<button
|
||||
name="action_open_model_sizes"
|
||||
type="object"
|
||||
icon="fa-arrow-right"
|
||||
title="Details"
|
||||
context="{'measurement_date': measurement_date, 'historical_measurement_date': historical_measurement_date}"
|
||||
/>
|
||||
<field name="historical_total_model_size" optional="hide" />
|
||||
<field name="total_model_size" optional="hide" />
|
||||
<field name="diff_total_model_size" />
|
||||
<field name="historical_total_database_size" optional="hide" />
|
||||
<field name="total_database_size" optional="hide" />
|
||||
<field name="diff_total_database_size" />
|
||||
<field name="historical_table_size" optional="hide" />
|
||||
<field name="table_size" optional="hide" />
|
||||
<field name="historical_indexes_size" optional="hide" />
|
||||
<field name="indexes_size" optional="hide" />
|
||||
<field name="historical_tuples" optional="hide" />
|
||||
<field name="tuples" optional="hide" />
|
||||
<field name="historical_attachment_size" optional="hide" />
|
||||
<field name="attachment_size" optional="hide" />
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
<record id="ir_model_size_report_action" model="ir.actions.act_window">
|
||||
<field name="name">Compare Database Size per Model</field>
|
||||
<field name="res_model">ir.model.size.report</field>
|
||||
<field name="view_mode">list</field>
|
||||
<field name="context">{"search_default_filter_diff_total_model_size": 1}</field>
|
||||
</record>
|
||||
<menuitem
|
||||
action="ir_model_size_report_action"
|
||||
id="ir_model_size_report_menu"
|
||||
name="Compare Size per Model"
|
||||
parent="database_size_menu"
|
||||
sequence="20"
|
||||
/>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user