Merge pull request #4 from expsa/web_tree_and_ps_report

migration
This commit is contained in:
esam-sermah 2025-09-21 17:31:30 +03:00 committed by GitHub
commit fa1c349de2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 1327 additions and 0 deletions

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models

View File

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) PySquad Informetics (<https://www.pysquad.com/>).
#
# 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,
}

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import dynamic_report_configure
from . import inherit_ir_actions

View File

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

View File

@ -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')

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<template id="report_dynamic">
<t t-call="web.html_container">
<t t-call="web.external_layout">
<style>
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #dddddd;
}
</style>
<div class="page">
<center>
<h3>
<strong style="text-align:center;">
<t t-esc="report_name"/>
</strong>
</h3>
</center>
<br/>
<table>
<tr>
<t t-foreach="table_heading" t-as="line">
<th name="th_description" class="text-left">
<span t-esc="line"/>
</th>
</t>
</tr>
<tbody>
<t t-foreach="record_data" t-as="record">
<tr>
<t t-foreach="record" t-as="rec">
<td>
<span t-esc="rec"/>
</td>
</t>
</tr>
</t>
<tr>
<t t-foreach="total_data" t-as="total">
<td>
<span t-esc="total"/>
</td>
</t>
</tr>
</tbody>
</table>
</div>
</t>
</t>
</template>
<record id="action_dynamic_report_print" model="ir.actions.report">
<field name="name">Report</field>
<field name="model">dynamic.report.configure</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">ps_dynamic_report.report_dynamic</field>
<field name="report_file">ps_dynamic_report.report_dynamic</field>
<field name="binding_type">report</field>
</record>
</data>
</odoo>

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_dynamic_report_configure dynamic_report_configure model_dynamic_report_configure 1 1 1 1
3 access_dynamic_report_field dynamic_report_field model_dynamic_report_field 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -0,0 +1,230 @@
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link href="/multi_company_selector/static/src/css/demo.css" media="screen, print" rel="stylesheet"
type="text/css"/>
<title>Hello, web</title>
<style>
</style>
</head>
<body>
<section style="padding-bottom:1vh;margin-top: 8vh;">
<header>
<div class="navbar navbar-light shadow-sm " style="border-bottom: 1px solid #d5d5d5;">
<div class="container d-flex justify-content-between">
<div class="col-md-3">
<a href="#" class="navbar-brand d-flex align-items-center">
<img class="img-fluid d-block mx-auto" src="images/logo.jpeg">
</a>
</div>
<div class="my-3 d-flex align-items-center">
<div style="background-color:#7C7BAD !important; color:#fff !important; font-weight:600 !important; padding:5px 15px 8px !important; margin:0 5px !important">
<i class="fa fa-check mr-1"></i>Community
</div>
<div style="background-color:#8F4D7D !important; color:#fff !important; font-weight:600 !important; padding:5px 15px 8px !important; margin:0 5px !important">
<i class="fa fa-check mr-1"></i>Enterprise
</div>
</div>
</div>
</div>
</header>
<div class="container">
<div class="row justify-content-center pt-5">
<div class="col-md-10">
<strong>
<p>
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.
</p>
</strong>
</div>
</div>
</div>
</section>
<section style="padding:3.5rem 1.5rem !important; background-color:#ffffff !important; padding-bottom:1vh">
<div class="conntainer">
<div class="row">
<div class="col-md-12">
<img class="img-fluid d-block mx-auto border border-secondry"
src="/images/ps_image1.png">
<h5 style="padding-top:3vh" class="text-center">Go to Dynamic Report Configure under(settings)</h5>
</div>
</div>
</div>
</section>
<section style="padding:3.5rem 1.5rem !important; background-color:#ffffff !important; padding-bottom:1vh">
<div class="conntainer">
<div class="row">
<div class="col-md-12">
<img class="img-fluid d-block mx-auto border border-secondry"
src="/images/ps_image2.png">
<div class="row justify-content-center">
<div class="col-md-6">
<h5 style="padding-top:3vh; text-align: left !important;" class="text-center">Create a one
record</h5>
<ul>
<li>Select the model which you want to create of his Server action</li>
<li>Choose a Report Type PDF or XLS</li>
<li>And if you want a total of any fields, Just enable a Sum checkbox.</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</section>
<section style="padding:3.5rem 1.5rem !important; background-color:#ffffff !important; padding-bottom:1vh">
<div class="conntainer">
<div class="row">
<div class="col-md-12">
<img class="img-fluid d-block mx-auto border border-secondry"
src="/images/ps_image3.png">
<h5 style="padding-top:3vh" class="text-center">Click on Create Action Button to create a server
action.</h5>
</div>
</div>
</div>
</section>
<section style="padding:3.5rem 1.5rem !important; background-color:#ffffff !important; padding-bottom:1vh">
<div class="conntainer">
<div class="row">
<div class="col-md-12">
<img class="img-fluid d-block mx-auto border border-secondry"
src="/images/ps_image4.png">
<h5 style="padding-top:3vh" class="text-center">Above you can see a action is created. Now, Click on it
to Download Report</h5>
</div>
</div>
</div>
</section>
<section style="padding:3.5rem 1.5rem !important; background-color:#ffffff !important; padding-bottom:1vh">
<div class="conntainer">
<div class="row">
<div class="col-md-12">
<img class="img-fluid d-block mx-auto border border-secondry"
src="/images/ps_image5.png">
<h5 style="padding-top:3vh" class="text-center">Pdf report.</h5>
</div>
</div>
</div>
</section>
<section style="padding:3.5rem 1.5rem !important; background-color:#ffffff !important; padding-bottom:1vh">
<div class="conntainer">
<div class="row">
<div class="col-md-12">
<img class="img-fluid d-block mx-auto border border-secondry"
src="/images/ps_image6.png">
<h5 style="padding-top:3vh" class="text-center">To Create a XLS report action.</h5>
</div>
</div>
</div>
</section>
<section style="padding:3.5rem 1.5rem !important; background-color:#ffffff !important; padding-bottom:1vh">
<div class="conntainer">
<div class="row">
<div class="col-md-12">
<img class="img-fluid d-block mx-auto border border-secondry"
src="/images/ps_image7.png">
<h5 style="padding-top:3vh" class="text-center"></h5>
</div>
</div>
</div>
</section>
<section style="padding:3.5rem 1.5rem !important; background-color:#ffffff !important; padding-bottom:1vh">
<div class="conntainer">
<div class="row">
<div class="col-md-12">
<img class="img-fluid d-block mx-auto border border-secondry"
src="/images/ps_image8.png">
<h5 style="padding-top:3vh" class="text-center">Xls Report</h5>
</div>
</div>
</div>
</section>
<section style="padding:3.5rem 1.5rem">
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="text-left" style="width:100%">
<h2 style="letter-spacing:2px; font-weight:800;font-size: 2rem; color:#000; text-transform:uppercase; font-family:'Montserrat', sans-serif">
HELP AND SUPPORT
</h2>
<span class="o_gradient" style="width:280px; height:2px; display:inline-block; border-radius:2px; background: linear-gradient(
150deg, #875a7b 20%, #62495b 80%) !important;"></span>
<div class="row" style="border-bottom: 1px solid #000; padding-bottom: 9vh;">
<div class="col-lg-12 d-flex justify-content-center">
<span class="btn btn-danger btn-lg" style="margin-top:4vh;background-color:#e86874;"><i
class="fa fa-question-circle align-middle"></i> For any Query</span>
</div>
</div>
<div class="row" style="padding-top: 1vh; background-color:#DAD9DF">
<div class="col-lg-4 text-center">
<img width="40px" align="center" height="40px" src="images/gmail.png">
<h4 style="padding-top:10px">Write a mail to us</h4>
<h3 style="color: #75b1ab;"><a href="contact@pysquad.com" target="_blank"
style="color: #393185;">contact@pysquad.com</a></h3>
</div>
<div class="col-lg-4 text-center">
<img width="40px" align="center" height="40px" src="images/whatsapp.png">
<h4 style="padding-top:10px">Write a text To us on WhatsApp</h4>
<h3><a href="http://wa.me/919825241294" target="_blank" style="color: #393185;">+91
9825241294</a></h3>
</div>
<div class="col-lg-4 text-center">
<img width="40px" align="center" height="40px" src="images/web.png">
<h4 style="padding-top:10px">Visit Our Wesite</h4>
<h3><a href="https://pysquad.com/" target="_blank"
style="color: #393185;">www.pysquad.com</a></h3>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossorigin="anonymous"></script>
</body>
</html>

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="dynamic_report_configure_form_view" model="ir.ui.view">
<field name="name">dynamic.report.configure.view.form</field>
<field name="model">dynamic.report.configure</field>
<field name="arch" type="xml">
<form string="Dynamic Report Configure">
<header>
<button name="create_server_action" string="Create Action" type="object" class="oe_highlight"
invisible="is_action_created != False"/>
<button name="remove_server_action" string="Remove Action" type="object"
invisible="is_action_created == False"/>
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="name" required="1" readonly="is_action_created != False"/>
</h1>
</div>
<group>
<group>
<!-- ✅ حذف context المشكل -->
<field name="model_id" required="1" readonly="is_action_created != False"/>
<field name="is_action_created" invisible="1"/>
<field name="server_action_id" invisible="1"/>
</group>
<group>
<field name="report_type" required="1" widget="radio" options="{'horizontal': true}" readonly="is_action_created != False"/>
</group>
</group>
<field name="dynamic_field_id" context="{'dynamic_field_id':dynamic_field_id}" nolabel="1" readonly="is_action_created != False" invisible="model_id == False">
<list string="Dynamic Field" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="field_id" string="Field Name" options="{'no_create': True}"
domain="[('model_id', '=', parent.model_id), ('ttype', 'not in' , ['one2many', 'many2many'])]"/>
<field name="field_name_id" string="Technical name"/>
<field name="field_type" string="Field Type"/>
<field name="dynamic_configure_id" invisible="1"/>
<field name="is_sum_calc" invisible="field_type not in ['float', 'integer', 'monetary']"/>
</list>
<form>
<group>
<group>
<field name="field_id" string="Field Name"/>
<field name="field_name_id" string="Technical Name"/>
</group>
<group>
<field name="field_type"/>
<field name="dynamic_configure_id"/>
</group>
</group>
</form>
</field>
</sheet>
</form>
</field>
</record>
<record id="dynamic_report_configure_tree_view" model="ir.ui.view">
<field name="name">dynamic.report.configure.view.tree</field>
<field name="model">dynamic.report.configure</field>
<field name="arch" type="xml">
<list string="Configuration">
<field name="name" string="Name"/>
<field name="model_id" string="Model Name"/>
</list>
</field>
</record>
<record id="action_dynamic_report_configure" model="ir.actions.act_window">
<field name="name">Dynamic Report</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">dynamic.report.configure</field>
<field name="view_mode">list,form</field>
</record>
<menuitem action="action_dynamic_report_configure"
name="Dynamic Report"
id="menu_dynamic_report_configure"
parent="base.reporting_menuitem"
sequence="10"/>
</odoo>

View File

@ -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 <https://github.com/OCA/server-ux/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 <https://github.com/OCA/server-ux/issues/new?body=module:%20customized_list_view%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Ilyas
* Ooops
Contributors
~~~~~~~~~~~~
* `Ooops404 <https://www.ooops404.com>`__:
* Ilyas <irazor147@gmail.com>
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 <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-ilyasProgrammer|
This module is part of the `OCA/server-ux <https://github.com/OCA/server-ux/tree/14.0/customized_list_view>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -0,0 +1,2 @@
from . import models
from .hooks import uninstall_hook

View File

@ -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",
}

View File

@ -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()

View File

@ -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 <francesco.foresti@ooops404.com>\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"

View File

@ -0,0 +1,4 @@
from . import custom_list
from . import customer_list_view_line
from . import ir_ui_view
from . import ir_module

View File

@ -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 = (
"<field name='%s' widget='%s' optional='%s' string='%s'/>"
% (
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

View File

@ -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)])

View File

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

View File

@ -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]

View File

@ -0,0 +1,3 @@
* `Ooops404 <https://www.ooops404.com>`__:
* Ilyas <irazor147@gmail.com>

View File

@ -0,0 +1,2 @@
Using this module you can add new fields into list views without having deep odoo
technical knowledge.

View File

@ -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.

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 clw1 clw1 model_custom_list_view base.group_system 1 1 1 1
3 clwl1 clwl1 model_custom_list_view_line base.group_system 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,3 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_customized_list_view

View File

@ -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.")

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_custom_list_view_form" model="ir.ui.view">
<field name="name">custom.list.view.form.view</field>
<field name="model">custom.list.view</field>
<field name="arch" type="xml">
<form string="Customized List View">
<header>
<button
name="button_apply_changes"
type="object"
string="Apply"
groups="base.group_system"
class="btn-primary"
/>
<button
name="button_roll_back"
type="object"
string="Restore Original"
groups="base.group_system"
class="btn-secondary"
/>
</header>
<sheet>
<group>
<group>
<field name="name" />
<field name="model_id" />
<field name="model_name" />
<field
name="list_view_id"
domain="[('model', '=', model_name), ('type', 'in', ['tree', 'list']), ('mode', '=', 'primary')]"
readonly="model_name == False"
/>
</group>
</group>
<field name="line_ids">
<list editable="top">
<field name="model_id" invisible="1" />
<field name="custom_list_view_id" invisible="1" />
<field name="model_name" />
<field
name="field_id"
domain="[('model_id', '=', model_id)]"
/>
<field name="label" />
<field name="use_widget" />
<field name="optional" />
<field name="fields_domain" invisible="1" />
<field
name="before"
required="after == False"
readonly="after != False"
domain="[('model_id', '=', model_id)]"
/>
<field
name="after"
domain="[('model_id', '=', model_id)]"
required="before == False"
readonly="before != False"
/>
</list>
</field>
</sheet>
</form>
</field>
</record>
<record id="view_custom_list_view_tree" model="ir.ui.view">
<field name="name">custom.list.view.tree.view</field>
<field name="model">custom.list.view</field>
<field name="arch" type="xml">
<list>
<field name="model_id" />
<field name="list_view_id" />
</list>
</field>
</record>
<record id="action_custom_list_view_tree" model="ir.actions.act_window">
<field name="name">Customized List Views</field>
<field name="res_model">custom.list.view</field>
<field name="view_mode">list,form</field>
<field name="target">current</field>
</record>
<menuitem
id="menu_custom_list_view_tree"
name="Customized List Views"
action="action_custom_list_view_tree"
parent="base.menu_administration"
sequence="20"
/>
</odoo>