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 + + + + + + +
+
+ + +
+
+
+
+ +

+ 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 +

+ +
+
+ For any Query +
+ +
+
+ + +
+ +

Write a mail to us

+

contact@pysquad.com

+
+ + +
+ +

Write a text To us on WhatsApp

+

+91 + 9825241294

+
+ + +
+ +

Visit Our Wesite

+

www.pysquad.com

+
+ + +
+
+
+
+
+
+ + + + + + + + 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 + + + + +