diff --git a/odex30_base/ps_dynamic_report/__init__.py b/odex30_base/ps_dynamic_report/__init__.py
new file mode 100644
index 0000000..cde864b
--- /dev/null
+++ b/odex30_base/ps_dynamic_report/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import models
diff --git a/odex30_base/ps_dynamic_report/__manifest__.py b/odex30_base/ps_dynamic_report/__manifest__.py
new file mode 100644
index 0000000..3fe62af
--- /dev/null
+++ b/odex30_base/ps_dynamic_report/__manifest__.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) PySquad Informetics ( ).
+#
+# For Module Support : contact@pysquad.com
+#
+##############################################################################
+
+{
+ # Module Information
+ "name": "Dynamic Report",
+ "version": "18.0.1.0.0",
+ "category": "Odex30-base",
+ "description": "Export to pdf and xls in one click",
+ "summary": """
+ Dynamic Report
+ Export Tree View PDF
+ Export Tree View XLSx
+ Creating Fully Dynamically Pdf & XLS report.
+ """,
+
+ # Author
+ "author": "Pysquad Informatics LLP",
+ "website": "https://www.pysquad.com",
+ "license": "LGPL-3",
+
+ # Dependencies
+ "depends": ["base"],
+
+ # Data File
+ "data": [
+ 'security/ir.model.access.csv',
+ 'report/dynamic_report.xml',
+ 'views/dynamic_report_configure.xml',
+ ],
+ 'images': [
+ 'static/description/banner_img.png',
+ ],
+
+ # Technical Specif.
+ 'installable': True,
+ 'application': False,
+ 'auto_install': False,
+}
diff --git a/odex30_base/ps_dynamic_report/models/__init__.py b/odex30_base/ps_dynamic_report/models/__init__.py
new file mode 100644
index 0000000..d9bf0a1
--- /dev/null
+++ b/odex30_base/ps_dynamic_report/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import dynamic_report_configure
+from . import inherit_ir_actions
diff --git a/odex30_base/ps_dynamic_report/models/dynamic_report_configure.py b/odex30_base/ps_dynamic_report/models/dynamic_report_configure.py
new file mode 100644
index 0000000..05b701a
--- /dev/null
+++ b/odex30_base/ps_dynamic_report/models/dynamic_report_configure.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError
+
+import xlwt
+import base64
+from io import BytesIO
+
+
+class DynamicReportConfigure(models.Model):
+ _name = 'dynamic.report.configure'
+ _rec_name = 'name'
+
+ name = fields.Char('Name')
+ model_id = fields.Many2one('ir.model', 'Model', domain="[('transient', '=', False)]")
+ dynamic_field_id = fields.One2many('dynamic.report.field', 'dynamic_configure_id', 'Dynamic Field')
+ is_action_created = fields.Boolean('Is Created?', default=False)
+ server_action_id = fields.Many2one('ir.actions.server', 'Server id')
+ report_type = fields.Selection([('pdf', 'PDF'), ('xls', 'XLS')], default='pdf', string="Report Type")
+
+ @api.onchange('model_id')
+ def _onchange_model_id(self):
+ if self.dynamic_field_id:
+ self.dynamic_field_id.unlink()
+
+ def create_dynamic_pdf_report(self, server):
+ server_id = self.env['ir.actions.server'].search([('dynamic_report_id', '=', server)])
+ dynamic_report_id = server_id.dynamic_report_id.dynamic_field_id.filtered(lambda a: a.field_id)
+ Heading_values = [item.field_id.field_description for item in dynamic_report_id]
+
+ record_data = []
+ total_data = []
+ active_model_id = self.env[self._context.get('active_model')].browse(self._context.get('active_ids'))
+ for model in active_model_id:
+ temp = []
+ for item in dynamic_report_id:
+ if item.field_type == 'selection':
+ state = dict(model._fields[item.field_name_id].selection).get(model[item.field_name_id])
+ temp.append(state)
+ elif item.field_type == 'datetime':
+ date = getattr(model, item.field_name_id)
+ temp.append(str(date.date()) if date else '')
+ elif item.field_type == 'date':
+ temp.append(str(getattr(model, item.field_name_id) or ''))
+ elif item.field_type == 'many2one':
+ temp_value = getattr(model, item.field_name_id)
+ value = getattr(temp_value, temp_value._rec_name)
+ temp.append(value)
+ else:
+ temp.append(getattr(model, item.field_name_id))
+
+ if len(total_data) < len(Heading_values):
+ if item.is_sum_calc:
+ m_value = sum(active_model_id.mapped(item.field_name_id))
+ total_data.append(m_value)
+ else:
+ total_data.append('')
+
+ record_data.append(temp)
+ data = {
+ 'report_name': server_id.dynamic_report_id.name,
+ 'model_id': server_id.dynamic_report_id.model_id.model,
+ 'name': server_id.dynamic_report_id.name,
+ 'table_heading': Heading_values,
+ 'record_data': record_data,
+ 'total_data': total_data
+ }
+ if server_id.dynamic_report_id.report_type == 'pdf':
+ return self.env.ref('ps_dynamic_report.action_dynamic_report_print').report_action(self, data=data)
+ else:
+ workbook = xlwt.Workbook(encoding='utf-8', style_compression=0)
+ fl = BytesIO()
+ worksheet = workbook.add_sheet('Report Details', cell_overwrite_ok=True)
+ font = xlwt.Font()
+
+ xlwt.add_palette_colour("custom_colour", 0x21)
+ workbook.set_colour_RGB(0x21, 245, 184, 103)
+ style1 = xlwt.easyxf("pattern: pattern solid, fore_colour custom_colour; alignment: vert centre, horiz center; font: name Arial, bold True;")
+ style2 = xlwt.easyxf("pattern: pattern solid, fore_colour silver_ega; alignment: horizontal center; font: name Arial, bold True;")
+ style3 = xlwt.easyxf('alignment: horizontal center;')
+ style4 = xlwt.easyxf('alignment: horizontal center; font: name Arial, bold True;')
+
+ cell_value = xlwt.XFStyle()
+ cell_value.font = font
+
+ worksheet.write_merge(0, 1, 0, len(Heading_values)-1, server_id.dynamic_report_id.name or '', style1)
+
+ row = 3
+ col = 0
+ for heading in Heading_values:
+ worksheet.col(col).width = 500 * 12
+ worksheet.write(row, col, heading, style2)
+ col += 1
+
+ row = 4
+ col = 0
+ for data in record_data:
+ for rec in data:
+ worksheet.write(row, col, rec, style3)
+ col += 1
+ col = 0
+ row += 1
+
+ col = 0
+ for total in total_data:
+ worksheet.write(row, col, total, style4)
+ col += 1
+
+ filename = server_id.dynamic_report_id.name or "Report Detail"
+ workbook.save(fl)
+ fl.seek(0)
+ test = base64.encodebytes(fl.read())
+ attach_vals = {
+ 'name': '%s.xlsx' % (filename),
+ 'datas': test,
+ }
+ doc_id = self.env['ir.attachment'].create(attach_vals)
+ return {
+ 'type': 'ir.actions.act_url',
+ 'url': 'web/content/%s?download=true' % (doc_id.id),
+ 'target': 'self',
+ }
+
+ def create_server_action(self):
+ if not self.dynamic_field_id:
+ raise UserError(_("Please Select few Fields!!"))
+ vals = {
+ 'name': self.name if self.name else 'Custom Action',
+ 'model_id': self.model_id.id,
+ 'model_name': self.model_id.name,
+ 'state': 'code',
+ 'binding_model_id': self.model_id.id,
+ 'binding_view_types': 'list',
+ 'dynamic_report_id': self.id,
+ 'code': "action = env['dynamic.report.configure'].create_dynamic_pdf_report(server={server_id})".format(server_id=self.id)
+ }
+ server_action_id = self.env['ir.actions.server'].create(vals)
+ self.server_action_id = server_action_id.id
+ self.is_action_created = True
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'reload',
+ }
+
+ def remove_server_action(self):
+ if self.is_action_created and self.server_action_id:
+ self.server_action_id.unlink()
+ self.is_action_created = False
+
+
+class DynamicReportField(models.Model):
+ _name = 'dynamic.report.field'
+
+ field_id = fields.Many2one('ir.model.fields', 'Field')
+ sequence = fields.Integer('..', help="Gives the sequence order when displaying a list O2m.")
+ field_name_id = fields.Char(related='field_id.name', string='Target Model Name', readonly=True)
+ field_type = fields.Selection(related='field_id.ttype', string='Field Type')
+ dynamic_configure_id = fields.Many2one('dynamic.report.configure', 'Reference')
+ is_sum_calc = fields.Boolean("Sum")
+
+ @api.model
+ def default_get(self, fields):
+ res = super(DynamicReportField, self).default_get(fields)
+ if self._context:
+ context_keys = self._context.keys()
+ next_sequence = 1
+ if 'dynamic_field_id' in context_keys:
+ if len(self._context.get('dynamic_field_id')) > 0:
+ next_sequence = len(self._context.get('dynamic_field_id')) + 1
+ res.update({'sequence': next_sequence})
+ return res
diff --git a/odex30_base/ps_dynamic_report/models/inherit_ir_actions.py b/odex30_base/ps_dynamic_report/models/inherit_ir_actions.py
new file mode 100644
index 0000000..1691cc8
--- /dev/null
+++ b/odex30_base/ps_dynamic_report/models/inherit_ir_actions.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+from odoo import api, fields, models, _
+
+
+class InheritIrActionsServer(models.Model):
+ _inherit = 'ir.actions.server'
+
+ dynamic_report_id = fields.Many2one('dynamic.report.configure', 'Dynamic Reference')
diff --git a/odex30_base/ps_dynamic_report/report/dynamic_report.xml b/odex30_base/ps_dynamic_report/report/dynamic_report.xml
new file mode 100644
index 0000000..486b8a5
--- /dev/null
+++ b/odex30_base/ps_dynamic_report/report/dynamic_report.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Report
+ dynamic.report.configure
+ qweb-pdf
+ ps_dynamic_report.report_dynamic
+ ps_dynamic_report.report_dynamic
+ report
+
+
+
+
\ No newline at end of file
diff --git a/odex30_base/ps_dynamic_report/security/ir.model.access.csv b/odex30_base/ps_dynamic_report/security/ir.model.access.csv
new file mode 100644
index 0000000..6ff0955
--- /dev/null
+++ b/odex30_base/ps_dynamic_report/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_dynamic_report_configure,dynamic_report_configure,model_dynamic_report_configure,,1,1,1,1
+access_dynamic_report_field,dynamic_report_field,model_dynamic_report_field,,1,1,1,1
diff --git a/odex30_base/ps_dynamic_report/static/description/banner_img.png b/odex30_base/ps_dynamic_report/static/description/banner_img.png
new file mode 100644
index 0000000..44541dc
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/banner_img.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/icon.png b/odex30_base/ps_dynamic_report/static/description/icon.png
new file mode 100644
index 0000000..4754100
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/icon.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/images/gmail.png b/odex30_base/ps_dynamic_report/static/description/images/gmail.png
new file mode 100644
index 0000000..689c36d
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/images/gmail.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/images/logo.jpeg b/odex30_base/ps_dynamic_report/static/description/images/logo.jpeg
new file mode 100644
index 0000000..a07b2c1
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/images/logo.jpeg differ
diff --git a/odex30_base/ps_dynamic_report/static/description/images/ps_image1.png b/odex30_base/ps_dynamic_report/static/description/images/ps_image1.png
new file mode 100644
index 0000000..d6f7fbd
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/images/ps_image1.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/images/ps_image2.png b/odex30_base/ps_dynamic_report/static/description/images/ps_image2.png
new file mode 100644
index 0000000..1a86f98
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/images/ps_image2.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/images/ps_image3.png b/odex30_base/ps_dynamic_report/static/description/images/ps_image3.png
new file mode 100644
index 0000000..9afafb2
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/images/ps_image3.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/images/ps_image4.png b/odex30_base/ps_dynamic_report/static/description/images/ps_image4.png
new file mode 100644
index 0000000..cf3f76b
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/images/ps_image4.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/images/ps_image5.png b/odex30_base/ps_dynamic_report/static/description/images/ps_image5.png
new file mode 100644
index 0000000..db0d76d
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/images/ps_image5.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/images/ps_image6.png b/odex30_base/ps_dynamic_report/static/description/images/ps_image6.png
new file mode 100644
index 0000000..b2a0cf8
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/images/ps_image6.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/images/ps_image7.png b/odex30_base/ps_dynamic_report/static/description/images/ps_image7.png
new file mode 100644
index 0000000..d235b50
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/images/ps_image7.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/images/ps_image8.png b/odex30_base/ps_dynamic_report/static/description/images/ps_image8.png
new file mode 100644
index 0000000..5509a6a
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/images/ps_image8.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/images/web.png b/odex30_base/ps_dynamic_report/static/description/images/web.png
new file mode 100644
index 0000000..ebc528d
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/images/web.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/images/whatsapp.png b/odex30_base/ps_dynamic_report/static/description/images/whatsapp.png
new file mode 100644
index 0000000..a8d0a8c
Binary files /dev/null and b/odex30_base/ps_dynamic_report/static/description/images/whatsapp.png differ
diff --git a/odex30_base/ps_dynamic_report/static/description/index.html b/odex30_base/ps_dynamic_report/static/description/index.html
new file mode 100644
index 0000000..92fd66a
--- /dev/null
+++ b/odex30_base/ps_dynamic_report/static/description/index.html
@@ -0,0 +1,230 @@
+
+
+
+
+
+
+
+
+
+
+ Hello, web
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Community
+
+
+ Enterprise
+
+
+
+
+
+
+
+
+
+
+ This Module helps you to create Fully Dynamically Pdf & XLS report. You Can also Create Multiple
+ Action of Same Model and Select Fields according to Show in Report Just one Click.
+
+
+
+
+
+
+
+
+
+
+
+
+
Go to Dynamic Report Configure under(settings)
+
+
+
+
+
+
+
+
+
+
+
+
+
Create a one
+ record
+
+ Select the model which you want to create of his Server action
+ Choose a Report Type PDF or XLS
+ And if you want a total of any fields, Just enable a Sum checkbox.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Click on Create Action Button to create a server
+ action.
+
+
+
+
+
+
+
+
+
+
+
Above you can see a action is created. Now, Click on it
+ to Download Report
+
+
+
+
+
+
+
+
+
+
+
Pdf report.
+
+
+
+
+
+
+
+
+
+
+
To Create a XLS report action.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Xls Report
+
+
+
+
+
+
+
+
+
+
+
+ HELP AND SUPPORT
+
+
+
+
+
+
+
+
+
Write a mail to us
+
+
+
+
+
+
+
Write a text To us on WhatsApp
+
+
+
+
+
+
+
Visit Our Wesite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/odex30_base/ps_dynamic_report/views/dynamic_report_configure.xml b/odex30_base/ps_dynamic_report/views/dynamic_report_configure.xml
new file mode 100644
index 0000000..d29434d
--- /dev/null
+++ b/odex30_base/ps_dynamic_report/views/dynamic_report_configure.xml
@@ -0,0 +1,83 @@
+
+
+
+ dynamic.report.configure.view.form
+ dynamic.report.configure
+
+
+
+
+
+
+ dynamic.report.configure.view.tree
+ dynamic.report.configure
+
+
+
+
+
+
+
+
+
+ Dynamic Report
+ ir.actions.act_window
+ dynamic.report.configure
+ list,form
+
+
+
+
diff --git a/odex30_base/web_tree_customized_field_list/README.rst b/odex30_base/web_tree_customized_field_list/README.rst
new file mode 100644
index 0000000..a9ade93
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/README.rst
@@ -0,0 +1,102 @@
+====================
+Customized List View
+====================
+
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:3a84bbcf7504991f8ea35e9c11270c99debf913dad0f53c5bc0c402e10372871
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--ux-lightgray.png?logo=github
+ :target: https://github.com/OCA/server-ux/tree/14.0/customized_list_view
+ :alt: OCA/server-ux
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/server-ux-14-0/server-ux-14-0-customized_list_view
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/server-ux&target_branch=14.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+Using this module you can add new fields into list views without having deep odoo
+technical knowledge.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Usage
+=====
+
+* Enable debug mode.
+* Go to Settings.
+* Click Customized List Views in the top menu.
+* Create new record.
+* Select model and list view.
+* Create new line. Select which field you want to add, before or after each field.
+* Select widget and optional if required. Optional can be 'show' or 'hide'.
+* Save record and click Apply.
+* Open list view you modified. You will see new field there.
+* You can click Restore Original to restore original view.
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+~~~~~~~
+
+* Ilyas
+* Ooops
+
+Contributors
+~~~~~~~~~~~~
+
+* `Ooops404 `__:
+
+ * Ilyas
+
+Maintainers
+~~~~~~~~~~~
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+.. |maintainer-ilyasProgrammer| image:: https://github.com/ilyasProgrammer.png?size=40px
+ :target: https://github.com/ilyasProgrammer
+ :alt: ilyasProgrammer
+
+Current `maintainer `__:
+
+|maintainer-ilyasProgrammer|
+
+This module is part of the `OCA/server-ux `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/odex30_base/web_tree_customized_field_list/__init__.py b/odex30_base/web_tree_customized_field_list/__init__.py
new file mode 100644
index 0000000..071962a
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from .hooks import uninstall_hook
diff --git a/odex30_base/web_tree_customized_field_list/__manifest__.py b/odex30_base/web_tree_customized_field_list/__manifest__.py
new file mode 100644
index 0000000..3d788c9
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/__manifest__.py
@@ -0,0 +1,14 @@
+{
+ "name": "Customized List View",
+ "summary": "Add fields into list view",
+ "version": "18.0.1.0.0",
+ "category": "Usability",
+ "website": "https://github.com/OCA/server-ux",
+ "author": "Ilyas, Ooops, Odoo Community Association (OCA)",
+ "maintainers": ["ilyasProgrammer"],
+ "data": ["views/views.xml", "security/ir.model.access.csv"],
+ "depends": ["web_domain_field"],
+ "license": "AGPL-3",
+ "installable": True,
+ "uninstall_hook": "uninstall_hook",
+}
diff --git a/odex30_base/web_tree_customized_field_list/hooks.py b/odex30_base/web_tree_customized_field_list/hooks.py
new file mode 100644
index 0000000..079cc33
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/hooks.py
@@ -0,0 +1,12 @@
+# Copyright 2024 ooops404
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from odoo import SUPERUSER_ID, api
+
+
+def uninstall_hook(cr, registry):
+ # Restore all views
+ env = api.Environment(cr, SUPERUSER_ID, {})
+ customizations = env["custom.list.view"].with_context(active_test=False).search([])
+ for cust in customizations:
+ cust.button_roll_back()
diff --git a/odex30_base/web_tree_customized_field_list/i18n/it.po b/odex30_base/web_tree_customized_field_list/i18n/it.po
new file mode 100644
index 0000000..5ac54e3
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/i18n/it.po
@@ -0,0 +1,217 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_tree_customized_field_list
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 14.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2024-05-03 19:36+0000\n"
+"Last-Translator: Francesco Foresti \n"
+"Language-Team: none\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.17\n"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__active
+msgid "Active"
+msgstr "Attivo"
+
+#. module: web_tree_customized_field_list
+#: model_terms:ir.ui.view,arch_db:web_tree_customized_field_list.view_custom_list_view_form
+msgid "Apply"
+msgstr "Applica"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__create_uid
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__create_uid
+msgid "Created by"
+msgstr "Creato da"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__create_date
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__create_date
+msgid "Created on"
+msgstr "Creato il"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model,name:web_tree_customized_field_list.model_custom_list_view
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__custom_list_view_id
+msgid "Custom List View"
+msgstr "Vista lista personalizzata"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model,name:web_tree_customized_field_list.model_custom_list_view_line
+msgid "Custom List View Line"
+msgstr "Riga lista vista personalizzata"
+
+#. module: web_tree_customized_field_list
+#: model_terms:ir.ui.view,arch_db:web_tree_customized_field_list.view_custom_list_view_form
+msgid "Customized List View"
+msgstr "Vista lista personalizzata"
+
+#. module: web_tree_customized_field_list
+#: model:ir.actions.act_window,name:web_tree_customized_field_list.action_custom_list_view_tree
+#: model:ir.ui.menu,name:web_tree_customized_field_list.menu_custom_list_view_tree
+msgid "Customized List Views"
+msgstr "Viste lista personalizzate"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__display_name
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__display_name
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_ir_module_module__display_name
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_ir_ui_view__display_name
+msgid "Display Name"
+msgstr "Nome visualizzato"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__fields_domain
+msgid "Fields Domain"
+msgstr "Dominio campi"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__id
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__id
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_ir_module_module__id
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_ir_ui_view__id
+msgid "ID"
+msgstr "ID"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__label
+msgid "Label"
+msgstr "Etichetta"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__archive_uid
+msgid "Last Archived by"
+msgstr "Ultima archiviazione di"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__archive_date
+msgid "Last Archived on"
+msgstr "Ultima archiviazione il"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view____last_update
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line____last_update
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_ir_module_module____last_update
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_ir_ui_view____last_update
+msgid "Last Modified on"
+msgstr "Ultima modifica il"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__write_uid
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__write_uid
+msgid "Last Updated by"
+msgstr "Ultimo aggiornamento di"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__write_date
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__write_date
+msgid "Last Updated on"
+msgstr "Ultimo aggiornamento il"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__line_ids
+msgid "Line"
+msgstr "Riga"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__list_view_id
+msgid "List View"
+msgstr "Vista lista"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__model_id
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__model_name
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__model_id
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__model_name
+msgid "Model"
+msgstr "Modello"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model,name:web_tree_customized_field_list.model_ir_module_module
+msgid "Module"
+msgstr "Modulo"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__name
+msgid "Name"
+msgstr "Nome"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__field_id
+msgid "New Field"
+msgstr "Nuovo campo"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.constraint,message:web_tree_customized_field_list.constraint_custom_list_view_unique_model_list_view_rec
+msgid ""
+"Only one record per view is allowed. Please modify existing record instead."
+msgstr ""
+"Solo un record per vista è permesso. Modifica invece il record esistente."
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__optional
+msgid "Optional"
+msgstr "Opzionale"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view__original_arch
+msgid "Original Arch"
+msgstr "Arch originale"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__after
+msgid "Place After"
+msgstr "Inserisci dopo di"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__before
+msgid "Place Before"
+msgstr "Inserisci prima di"
+
+#. module: web_tree_customized_field_list
+#: model_terms:ir.ui.view,arch_db:web_tree_customized_field_list.view_custom_list_view_form
+msgid "Restore Original"
+msgstr "Ripristina originale"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model,name:web_tree_customized_field_list.model_ir_ui_view
+msgid "View"
+msgstr "Vista"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields,field_description:web_tree_customized_field_list.field_custom_list_view_line__use_widget
+msgid "Widget"
+msgstr "Widget"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields.selection,name:web_tree_customized_field_list.selection__custom_list_view_line__use_widget__color
+msgid "color"
+msgstr "color"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields.selection,name:web_tree_customized_field_list.selection__custom_list_view_line__optional__hide
+msgid "hide"
+msgstr "hide"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields.selection,name:web_tree_customized_field_list.selection__custom_list_view_line__use_widget__many2many_tags
+msgid "many2many_tags"
+msgstr "many2many_tags"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields.selection,name:web_tree_customized_field_list.selection__custom_list_view_line__use_widget__monetary
+msgid "monetary"
+msgstr "monetary"
+
+#. module: web_tree_customized_field_list
+#: model:ir.model.fields.selection,name:web_tree_customized_field_list.selection__custom_list_view_line__optional__show
+msgid "show"
+msgstr "show"
diff --git a/odex30_base/web_tree_customized_field_list/models/__init__.py b/odex30_base/web_tree_customized_field_list/models/__init__.py
new file mode 100644
index 0000000..3955558
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/models/__init__.py
@@ -0,0 +1,4 @@
+from . import custom_list
+from . import customer_list_view_line
+from . import ir_ui_view
+from . import ir_module
diff --git a/odex30_base/web_tree_customized_field_list/models/custom_list.py b/odex30_base/web_tree_customized_field_list/models/custom_list.py
new file mode 100644
index 0000000..04574c8
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/models/custom_list.py
@@ -0,0 +1,64 @@
+# Copyright 2024 ooops404
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from lxml import etree
+
+from odoo import api, fields, models
+
+
+class CustomListView(models.Model):
+ _name = "custom.list.view"
+ _description = "Custom List View"
+
+ active = fields.Boolean(default=True)
+ name = fields.Char(required=True)
+ model_id = fields.Many2one("ir.model", required=True, ondelete="cascade")
+ model_name = fields.Char(related="model_id.model", store=True)
+ list_view_id = fields.Many2one("ir.ui.view", required=True)
+ line_ids = fields.One2many("custom.list.view.line", "custom_list_view_id")
+ original_arch = fields.Text(readonly=True)
+
+ _sql_constraints = [
+ (
+ "unique_model_list_view_rec",
+ "UNIQUE(list_view_id)",
+ "Only one record per view is allowed. "
+ "Please modify existing record instead.",
+ )
+ ]
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ recs = super().create(vals_list)
+ for rec in recs:
+ rec.original_arch = rec.list_view_id.arch
+ return recs
+
+ def button_apply_changes(self):
+ self.ensure_one()
+ doc = etree.XML(self.original_arch)
+ for mod_line in self.line_ids:
+ target = mod_line.before and mod_line.before.name or mod_line.after.name
+ for node in doc.xpath("/tree/field[@name='%s']" % target):
+ node_string = (
+ " "
+ % (
+ mod_line.field_id.name,
+ mod_line.use_widget or "",
+ mod_line.optional or "",
+ mod_line.label or mod_line.field_id.field_description,
+ )
+ )
+ new_node = etree.fromstring(node_string)
+ if mod_line.before:
+ node.addprevious(new_node)
+ else:
+ node.addnext(new_node)
+ new_arch = etree.tostring(doc, encoding="unicode").replace("\t", "")
+ self.list_view_id.arch = new_arch
+
+ def button_roll_back(self):
+ self.ensure_one()
+ doc = etree.XML(self.original_arch)
+ new_arch = etree.tostring(doc, encoding="unicode").replace("\t", "")
+ self.list_view_id.arch = new_arch
diff --git a/odex30_base/web_tree_customized_field_list/models/customer_list_view_line.py b/odex30_base/web_tree_customized_field_list/models/customer_list_view_line.py
new file mode 100644
index 0000000..501cd72
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/models/customer_list_view_line.py
@@ -0,0 +1,55 @@
+# Copyright 2024 ooops404
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+import json
+
+from lxml import etree
+
+from odoo import api, fields, models
+
+
+class CustomListViewLine(models.Model):
+ _name = "custom.list.view.line"
+ _description = "Custom List View Line"
+
+ custom_list_view_id = fields.Many2one("custom.list.view")
+ model_id = fields.Many2one(related="custom_list_view_id.model_id")
+ model_name = fields.Char(related="custom_list_view_id.model_name")
+ field_id = fields.Many2one("ir.model.fields", "New Field")
+ after = fields.Many2one("ir.model.fields", "Place After")
+ before = fields.Many2one("ir.model.fields", "Place Before")
+ optional = fields.Selection([("show", "show"), ("hide", "hide")])
+ label = fields.Char()
+ use_widget = fields.Selection(
+ [
+ ("many2many_tags", "many2many_tags"),
+ ("color", "color"),
+ ("monetary", "monetary"),
+ ],
+ string="Widget",
+ default=False,
+ )
+ fields_domain = fields.Char(
+ compute="_compute_fields_domain",
+ readonly=True,
+ store=False,
+ )
+
+ @api.depends("custom_list_view_id.list_view_id")
+ def _compute_fields_domain(self):
+ for rec in self:
+ arch = (
+ rec.custom_list_view_id.original_arch
+ or rec.custom_list_view_id.list_view_id.arch
+ )
+ doc = etree.XML(arch)
+ nodes = doc.xpath("//tree//field")
+ field_names = []
+ for item in nodes:
+ field_names.append(item.attrib["name"])
+ field_ids = self.env["ir.model.fields"].search(
+ [
+ ("model", "=", rec.custom_list_view_id.model_name),
+ ("name", "in", field_names),
+ ]
+ )
+ rec.fields_domain = json.dumps([("id", "in", field_ids.ids)])
diff --git a/odex30_base/web_tree_customized_field_list/models/ir_module.py b/odex30_base/web_tree_customized_field_list/models/ir_module.py
new file mode 100644
index 0000000..01b6801
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/models/ir_module.py
@@ -0,0 +1,22 @@
+# Copyright 2024 ooops404
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+from odoo import models
+
+
+class Module(models.Model):
+ _inherit = "ir.module.module"
+
+ def _button_immediate_function(self, function):
+ res = super(Module, self)._button_immediate_function(function)
+ clv_model = self.env["ir.model"]._get("custom.list.view")
+ if not clv_model:
+ # case when customized_list_view was uninstalled
+ # views will be restored in the uninstall_hook
+ return res
+ line_mods = self.env["custom.list.view.line"].search([("field_id", "=", False)])
+ if line_mods:
+ # fields was deleted during modules operation
+ custom_views = line_mods.mapped("custom_list_view_id")
+ line_mods.unlink()
+ custom_views.button_apply_changes()
+ return res
diff --git a/odex30_base/web_tree_customized_field_list/models/ir_ui_view.py b/odex30_base/web_tree_customized_field_list/models/ir_ui_view.py
new file mode 100644
index 0000000..38095a6
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/models/ir_ui_view.py
@@ -0,0 +1,10 @@
+# Copyright 2024 ooops404
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+from odoo import models
+
+
+class IrUiView(models.Model):
+ _inherit = "ir.ui.view"
+
+ def name_get(self):
+ return [(rec.id, rec.xml_id) for rec in self]
diff --git a/odex30_base/web_tree_customized_field_list/readme/CONTRIBUTORS.rst b/odex30_base/web_tree_customized_field_list/readme/CONTRIBUTORS.rst
new file mode 100644
index 0000000..c29a9c7
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/readme/CONTRIBUTORS.rst
@@ -0,0 +1,3 @@
+* `Ooops404 `__:
+
+ * Ilyas
diff --git a/odex30_base/web_tree_customized_field_list/readme/DESCRIPTION.rst b/odex30_base/web_tree_customized_field_list/readme/DESCRIPTION.rst
new file mode 100644
index 0000000..1c5c5fe
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/readme/DESCRIPTION.rst
@@ -0,0 +1,2 @@
+Using this module you can add new fields into list views without having deep odoo
+technical knowledge.
diff --git a/odex30_base/web_tree_customized_field_list/readme/ROADMAP.rst b/odex30_base/web_tree_customized_field_list/readme/ROADMAP.rst
new file mode 100644
index 0000000..e69de29
diff --git a/odex30_base/web_tree_customized_field_list/readme/USAGE.rst b/odex30_base/web_tree_customized_field_list/readme/USAGE.rst
new file mode 100644
index 0000000..dc7badc
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/readme/USAGE.rst
@@ -0,0 +1,10 @@
+* Enable debug mode.
+* Go to Settings.
+* Click Customized List Views in the top menu.
+* Create new record.
+* Select model and list view.
+* Create new line. Select which field you want to add, before or after each field.
+* Select widget and optional if required. Optional can be 'show' or 'hide'.
+* Save record and click Apply.
+* Open list view you modified. You will see new field there.
+* You can click Restore Original to restore original view.
diff --git a/odex30_base/web_tree_customized_field_list/security/ir.model.access.csv b/odex30_base/web_tree_customized_field_list/security/ir.model.access.csv
new file mode 100644
index 0000000..e1e09d9
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+clw1,clw1,model_custom_list_view,base.group_system,1,1,1,1
+clwl1,clwl1,model_custom_list_view_line,base.group_system,1,1,1,1
diff --git a/odex30_base/web_tree_customized_field_list/static/description/icon.png b/odex30_base/web_tree_customized_field_list/static/description/icon.png
new file mode 100644
index 0000000..3a0328b
Binary files /dev/null and b/odex30_base/web_tree_customized_field_list/static/description/icon.png differ
diff --git a/odex30_base/web_tree_customized_field_list/tests/__init__.py b/odex30_base/web_tree_customized_field_list/tests/__init__.py
new file mode 100644
index 0000000..a521904
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/tests/__init__.py
@@ -0,0 +1,3 @@
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from . import test_customized_list_view
diff --git a/odex30_base/web_tree_customized_field_list/tests/test_customized_list_view.py b/odex30_base/web_tree_customized_field_list/tests/test_customized_list_view.py
new file mode 100644
index 0000000..7cf9e57
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/tests/test_customized_list_view.py
@@ -0,0 +1,83 @@
+# Copyright 2024 ooops404
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from odoo.tests import common
+
+
+class TestCustomizedListView(common.SavepointCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestCustomizedListView, cls).setUpClass()
+ cls.view_model = cls.env["ir.ui.view"].sudo()
+ cls.users_model = cls.env["ir.model"]._get("res.users")
+ cls.view_users_tree = cls.env.ref("base.view_users_tree")
+
+ def test_all_customized_list_view(self):
+ new_view = self.view_users_tree.copy()
+ # Lets create some custom.list.view records
+ login_field = self.env["ir.model.fields"].search(
+ [
+ ("model_id", "=", self.users_model.id),
+ ("name", "=", "login"),
+ ]
+ )
+ date_field = self.env["ir.model.fields"].search(
+ [
+ ("model_id", "=", self.users_model.id),
+ ("name", "=", "create_date"),
+ ]
+ )
+ write_field = self.env["ir.model.fields"].search(
+ [
+ ("model_id", "=", self.users_model.id),
+ ("name", "=", "write_date"),
+ ]
+ )
+ custom = self.env["custom.list.view"].create(
+ {
+ "name": "Test View Mod",
+ "model_id": self.users_model.id,
+ "list_view_id": self.view_users_tree.id,
+ }
+ )
+ self.env["custom.list.view.line"].create(
+ [
+ {
+ "custom_list_view_id": custom.id,
+ "field_id": date_field.id,
+ "after": login_field.id,
+ "label": "Custom Label 1",
+ },
+ {
+ "custom_list_view_id": custom.id,
+ "field_id": date_field.id,
+ "before": write_field.id,
+ "label": "Custom Label 2",
+ },
+ ]
+ )
+
+ # New field should not be in the view before button_apply_changes is clicked
+ users_form = self.view_users_tree.arch
+ self.assertNotIn(
+ "create_date", users_form, msg="create_date should not be in the view."
+ )
+
+ # Apply changes. Now new field should be there
+ custom.button_apply_changes()
+ users_form = self.view_users_tree.arch
+ self.assertIn(
+ "create_date", users_form, msg="create_date should be in the view."
+ )
+
+ # Try to roll back. New field should not be in the view.
+ custom.button_roll_back()
+ users_form = self.view_users_tree.arch
+ self.assertNotIn(
+ "create_date", users_form, msg="Roll back feature does not work."
+ )
+
+ # Trigger _compute_fields_domain with read(). No domain there.
+ custom.list_view_id = new_view
+ fields_domain = custom.line_ids[0].read(["fields_domain"])
+ self.assertIsNotNone(fields_domain, msg="fields_domain should be empty.")
diff --git a/odex30_base/web_tree_customized_field_list/views/views.xml b/odex30_base/web_tree_customized_field_list/views/views.xml
new file mode 100644
index 0000000..1b6c60b
--- /dev/null
+++ b/odex30_base/web_tree_customized_field_list/views/views.xml
@@ -0,0 +1,96 @@
+
+
+
+
+ custom.list.view.form.view
+ custom.list.view
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ custom.list.view.tree.view
+ custom.list.view
+
+
+
+
+
+
+
+
+
+ Customized List Views
+ custom.list.view
+ list,form
+ current
+
+
+
+
+