Merge PR #718 into 16.0

Signed-off-by legalsylvain
pull/795/head
OCA-git-bot 2023-10-10 14:26:01 +00:00
commit ace7828530
14 changed files with 122 additions and 234 deletions

View File

@ -17,14 +17,17 @@
], ],
"data": [ "data": [
"views/sql_export_view.xml", "views/sql_export_view.xml",
"views/ir_model_fields_view.xml",
"wizard/wizard_file_view.xml", "wizard/wizard_file_view.xml",
"security/sql_export_security.xml", "security/sql_export_security.xml",
"security/ir.model.access.csv", "security/ir.model.access.csv",
], ],
"demo": [ "demo": [
"demo/ir_model_fields.xml",
"demo/sql_export.xml", "demo/sql_export.xml",
], ],
"assets": {
"web.assets_backend": [
"sql_export/static/src/scss/modal_properties.scss",
]
},
"installable": True, "installable": True,
} }

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="date_field_variable_sql" model="ir.model.fields">
<field name="name">x_date</field>
<field name="field_description">Date</field>
<field name="ttype">date</field>
<field name="model_id" ref="sql_export.model_sql_file_wizard" />
<field name="model">sql.file.wizard</field>
<field name="state">manual</field>
</record>
<record id="integer_field_variable_sql" model="ir.model.fields">
<field name="name">x_id</field>
<field name="field_description">x_ID</field>
<field name="ttype">integer</field>
<field name="model_id" ref="sql_export.model_sql_file_wizard" />
<field name="model">sql.file.wizard</field>
<field name="state">manual</field>
</record>
<record id="m2m_field_variable_sql" model="ir.model.fields">
<field name="name">x_partner_categ_ids</field>
<field name="field_description">Partner Categories</field>
<field name="ttype">many2many</field>
<field name="model_id" ref="sql_export.model_sql_file_wizard" />
<field name="model">sql.file.wizard</field>
<field name="state">manual</field>
<field name="relation">res.partner.category</field>
</record>
</odoo>

View File

@ -30,14 +30,14 @@ SELECT p.id
FROM res_partner p FROM res_partner p
LEFT JOIN res_partner_res_partner_category_rel rel LEFT JOIN res_partner_res_partner_category_rel rel
ON rel.partner_id = p.id ON rel.partner_id = p.id
WHERE create_date &lt; %(x_date)s WHERE create_date > %(Date)s
AND id = %(x_id)s AND id = %(ID)s
AND rel.category_id in %(x_partner_categ_ids)s AND rel.category_id in %(Categories)s
]]> ]]>
</field> </field>
<field <field
eval="[(6, 0, [ref('date_field_variable_sql'), ref('integer_field_variable_sql'), ref('m2m_field_variable_sql')])]" name="query_properties_definition"
name="field_ids" eval="[{'name': '630eca383bc142e6', 'type': 'date', 'string': 'Date'}, {'name': '907ac618eccbab74', 'type': 'integer', 'string': 'ID'}, {'name': 'ec0556e22932334b', 'string': 'Categories', 'type': 'many2many', 'default': False, 'comodel': 'res.partner.category', 'domain': False}]"
/> />
</record> </record>

View File

@ -17,29 +17,8 @@ class SqlExport(models.Model):
file_format = fields.Selection([("csv", "CSV")], default="csv", required=True) file_format = fields.Selection([("csv", "CSV")], default="csv", required=True)
field_ids = fields.Many2many( use_properties = fields.Boolean(compute="_compute_use_properties")
"ir.model.fields", query_properties_definition = fields.PropertiesDefinition("Query Properties")
"fields_sqlquery_rel",
"sql_id",
"field_id",
"Parameters",
domain=[("model", "=", "sql.file.wizard"), ("state", "=", "manual")],
help="Before adding parameters, make sure you have created one that fill your "
"need in the dedicated menu with the right type and label. \n"
"Then, when you add a parameter here, you have to include it in the SQL "
"query in order to have dynamic values depending on the user choice.\n"
"The format of the parameters in the SQL query must be like this :"
" %(parameter_field_name)s. \n"
"Example : from the variable menu, create an variable with type 'char', "
"having field name 'x_name' and field label : 'Name' \n"
"Then, you can create a SQL query like this : "
"SELECT * FROM res_partner WHERE name = %(x_name)s the variable "
"can be used in any number of different SQL queries. \n"
"In the SQL query, you can also include these 2 special parameters "
"%(user_id)s and %(company_id)s which will be replaced respectively by "
"the user executing the query and the company of the user executing the"
" query.",
)
encoding = fields.Selection( encoding = fields.Selection(
[ [
@ -58,11 +37,27 @@ class SqlExport(models.Model):
default="utf-8", default="utf-8",
) )
def _compute_use_properties(self):
for rec in self:
rec.use_properties = bool(rec.query_properties_definition)
def configure_properties(self):
# we need a full window in order for property configuration to work, not a modal
wiz = self.env["sql.file.wizard"].create({"sql_export_id": self.id})
return {
"view_mode": "form",
"res_model": "sql.file.wizard",
"res_id": wiz.id,
"type": "ir.actions.act_window",
"context": self.env.context,
"nodestroy": True,
}
def export_sql_query(self): def export_sql_query(self):
self.ensure_one() self.ensure_one()
wiz = self.env["sql.file.wizard"].create({"sql_export_id": self.id}) wiz = self.env["sql.file.wizard"].create({"sql_export_id": self.id})
# no variable input, we can return the file directly # no variable input, we can return the file directly
if not self.field_ids: if not self.query_properties_definition:
return wiz.export_sql() return wiz.export_sql()
else: else:
return { return {
@ -88,3 +83,10 @@ class SqlExport(models.Model):
if self.encoding: if self.encoding:
res = res.decode(self.encoding) res = res.decode(self.encoding)
return res return res
def _check_execution(self):
self.ensure_one()
# only check execution if query does not contains variable
if self.query_properties_definition:
return True
return super()._check_execution()

View File

@ -1,4 +1,4 @@
Allow to export data in csv files FROM sql requests. Allow to export data in csv files FROM sql requests.
There are some restrictions in the sql query, you can only read datas. There are some restrictions in the sql query, you can only read datas.
No update, deletion or creation are possible. No update, deletion or creation are possible.
A new menu named Export is created. A new sub menu named Sql Export is available in the Dashboard main menu.

View File

@ -14,5 +14,3 @@ See sql_request_abstract module to fix this issue.
* checking SQL request by execution and rollback is disabled in this module * checking SQL request by execution and rollback is disabled in this module
since variables features has been introduced. This can be fixed by since variables features has been introduced. This can be fixed by
overloading _prepare_request_check_execution() function. overloading _prepare_request_check_execution() function.
* V16 : Restore export with parameters features.

View File

@ -6,5 +6,4 @@ Dashboards > Sql Export
- `%(company_id)s` allows to set in the query the company id of the user - `%(company_id)s` allows to set in the query the company id of the user
- `%(user_id)s` allows to set in the query the user id - `%(user_id)s` allows to set in the query the user id
- for any created field with `Sql Export Variables` menu, you can use it with `%(x_field_example)s` syntax - for any created property, you can use it with `%(Property String)s` syntax
(Limitation for relational fields)

View File

@ -0,0 +1,3 @@
.modal-body .o_field_property_add {
display: none;
}

View File

@ -4,7 +4,6 @@
import base64 import base64
from odoo import fields
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase, tagged from odoo.tests.common import TransactionCase, tagged
@ -61,15 +60,42 @@ class TestExportSqlQuery(TransactionCase):
sql_export.state, "sql_valid", "%s is a valid request" % (query) sql_export.state, "sql_valid", "%s is a valid request" % (query)
) )
def _test_sql_query_with_params(self): def test_sql_query_with_params(self):
query = self.env.ref("sql_export.sql_export_partner_with_variables") query = self.env.ref("sql_export.sql_export_partner_with_variables")
query.write({"state": "sql_valid"})
categ_id = self.env.ref("base.res_partner_category_0").id categ_id = self.env.ref("base.res_partner_category_0").id
wizard = self.wizard_obj.create( wizard = self.wizard_obj.create(
{ {
"sql_export_id": query.id, "sql_export_id": query.id,
"x_date": fields.Date.today(), }
"x_id": 1, )
"x_partner_categ_ids": [(6, 0, [categ_id])], wizard.write(
{
"query_properties": [
{
"name": "630eca383bc142e6",
"string": "Date",
"type": "date",
"default": "",
"value": "2023-02-03",
},
{
"name": "ec0556e22932334b",
"string": "Categories",
"type": "many2many",
"default": False,
"comodel": "res.partner.category",
"domain": False,
"value": [[categ_id, "Consulting Services"]],
},
{
"name": "907ac618eccbab74",
"string": "ID",
"type": "integer",
"default": False,
"value": 1,
},
]
} }
) )
wizard.export_sql() wizard.export_sql()

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="sql_parameter_view_form" model="ir.ui.view">
<field name="name">Sql_parameter_form_view</field>
<field name="model">ir.model.fields</field>
<field name="priority">150</field>
<field name="arch" type="xml">
<form string="SQL export">
<group>
<field name="name" />
<field name="field_description" />
<field name="ttype" />
<field
name="relation"
attrs="{'invisible': [('ttype', 'not in', ('many2one', 'many2many', 'one2many'))], 'required': [('ttype', 'in', ('many2one', 'many2many', 'one2many'))]}"
/>
<field name="model_id" readonly="1" />
<field name="model" invisible="1" />
<field name="required" />
</group>
</form>
</field>
</record>
<record id="sql_parameter_view_tree" model="ir.ui.view">
<field name="name">Sql_parameter_tree_view</field>
<field name="model">ir.model.fields</field>
<field name="priority">150</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="field_description" />
<field name="ttype" />
<field name="required" />
</tree>
</field>
</record>
<record id="sql_parameter_action" model="ir.actions.act_window">
<field name="name">SQL Parameter</field>
<field name="res_model">ir.model.fields</field>
<field name="view_mode">tree,form</field>
<field
name="context"
eval="{'default_model_id': ref('sql_export.model_sql_file_wizard'), 'default_size': 64, 'search_default_state': 'manual', 'default_model': 'sql.file.wizard'}"
/>
<field
name="domain"
>[('model','=','sql.file.wizard'), ('state', '=', 'manual')]</field>
</record>
<record id="sql_parameter_action_view_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="1" />
<field name="view_mode">tree</field>
<field name="view_id" ref="sql_parameter_view_tree" />
<field name="act_window_id" ref="sql_parameter_action" />
</record>
<record id="sql_parameter_action_view_form" model="ir.actions.act_window.view">
<field name="sequence" eval="2" />
<field name="view_mode">form</field>
<field name="view_id" ref="sql_parameter_view_form" />
<field name="act_window_id" ref="sql_parameter_action" />
</record>
<menuitem
id="sql_parameter_menu_view"
name="SQL Export Variables"
parent="spreadsheet_dashboard.spreadsheet_dashboard_menu_configuration"
action="sql_parameter_action"
sequence="50"
groups="sql_request_abstract.group_sql_request_manager"
/>
</odoo>

View File

@ -9,12 +9,15 @@
/> />
<field name="mode">primary</field> <field name="mode">primary</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="query" position="after">
<field name="use_properties" invisible="1" />
</field>
<button name="button_preview_sql_expression" position="attributes"> <button name="button_preview_sql_expression" position="attributes">
<attribute name="states" />
<attribute <attribute
name="attrs" name="attrs"
>{'invisible': [('field_ids', '!=', False)]}</attribute> >{'invisible': [('use_properties', '=', True)]}</attribute>
</button> </button>
<xpath expr="//header" position="inside"> <xpath expr="//header" position="inside">
<button <button
name="export_sql_query" name="export_sql_query"
@ -24,6 +27,14 @@
class="oe_highlight" class="oe_highlight"
icon="fa-arrow-right text-success" icon="fa-arrow-right text-success"
/> />
<button
name="configure_properties"
states="draft"
string="Configure Properties"
type="object"
class="oe_highlight"
icon="fa-arrow-right text-success"
/>
</xpath> </xpath>
<group name="group_main_info" position="inside"> <group name="group_main_info" position="inside">
<group name="option"> <group name="option">
@ -41,19 +52,14 @@
/> />
</group> </group>
</group> </group>
<group name="group_query" position="after"> <field name="query" position="before">
<group string="Parameters"> <p
<field colspan="2"
name="field_ids" attrs="{'invisible': [('use_properties', '=', False)]}"
nolabel="1" > In case of use of properties in the query, use this syntax : &#37;&#37;(Property String)s. <br
colspan="2"
options="{'no_create': True}"
context="{'tree_view_ref': 'sql_export.sql_parameter_view_tree', 'form_view_ref': 'sql_export.sql_parameter_view_form'}"
attrs="{'readonly': [('state', '!=', 'draft')]}"
groups="sql_request_abstract.group_sql_request_user"
/> />
</group> Example : SELECT id FROM sale_order WHERE create_date > &#37;&#37;(Start Date)s</p>
</group> </field>
</field> </field>
</record> </record>

View File

@ -2,12 +2,10 @@
# @author: Florian da Costa # @author: Florian da Costa
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import json
from datetime import datetime from datetime import datetime
from lxml import etree from odoo import _, fields, models
from odoo.exceptions import UserError
from odoo import _, api, fields, models
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
@ -18,71 +16,34 @@ class SqlFileWizard(models.TransientModel):
binary_file = fields.Binary("File", readonly=True) binary_file = fields.Binary("File", readonly=True)
file_name = fields.Char(readonly=True) file_name = fields.Char(readonly=True)
sql_export_id = fields.Many2one(comodel_name="sql.export", required=True) sql_export_id = fields.Many2one(comodel_name="sql.export", required=True)
query_properties = fields.Properties(
@api.model string="Properties",
def get_view(self, view_id=None, view_type="form", **options): definition="sql_export_id.query_properties_definition",
export_obj = self.env["sql.export"] copy=False,
sql_export = export_obj.browse(self.env.context.get("active_id")) )
result = super().get_view(view_id=view_id, view_type=view_type, **options)
if sql_export.field_ids:
etree.fromstring(result["arch"])
raise NotImplementedError(
_("The export with parameters is not implemented in V16")
)
return result
@api.model
def fields_view_get(
self, view_id=None, view_type="form", toolbar=False, submenu=False
):
"""
Display dynamically parameter fields depending on the sql_export.
"""
res = super(SqlFileWizard, self).fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu
)
export_obj = self.env["sql.export"]
if view_type == "form":
sql_export = export_obj.browse(self.env.context.get("active_id"))
if sql_export.field_ids:
eview = etree.fromstring(res["arch"])
group = etree.Element("group", name="variables_group", colspan="4")
toupdate_fields = []
for field in sql_export.field_ids:
toupdate_fields.append(field.name)
attrib = {"name": field.name, "required": "0", "readonly": "0"}
view_field = etree.SubElement(group, "field", attrib=attrib)
modifiers = json.loads(view_field.get("modifiers", "{}"))
if field.required:
modifiers["required"] = True
view_field.set("modifiers", json.dumps(modifiers))
res["fields"].update(self.fields_get(toupdate_fields))
placeholder = eview.xpath(
"//separator[@string='variables_placeholder']"
)[0]
placeholder.getparent().replace(placeholder, group)
res["arch"] = etree.tostring(eview, pretty_print=True)
return res
def export_sql(self): def export_sql(self):
self.ensure_one() self.ensure_one()
# Check properties
bad_props = [x for x in self.query_properties if not x["value"]]
if bad_props:
raise UserError(
_("Please enter a values for the following properties : %s")
% (",".join([x["string"] for x in bad_props]))
)
sql_export = self.sql_export_id sql_export = self.sql_export_id
# Manage Params # Manage Params
variable_dict = {} variable_dict = {}
now_tz = fields.Datetime.context_timestamp(sql_export, datetime.now()) now_tz = fields.Datetime.context_timestamp(sql_export, datetime.now())
date = now_tz.strftime(DEFAULT_SERVER_DATETIME_FORMAT) date = now_tz.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
if sql_export.field_ids: for prop in self.query_properties:
for field in sql_export.field_ids: if prop["type"] == "many2many":
if field.ttype == "many2one": variable_dict[prop["string"]] = tuple(prop["value"])
variable_dict[field.name] = self[field.name].id else:
elif field.ttype == "many2many": variable_dict[prop["string"]] = prop["value"]
variable_dict[field.name] = tuple(self[field.name].ids)
else:
variable_dict[field.name] = self[field.name]
if "%(company_id)s" in sql_export.query: if "%(company_id)s" in sql_export.query:
company_id = self.env.company.id company_id = self.env.company.id
variable_dict["company_id"] = company_id variable_dict["company_id"] = company_id

View File

@ -5,10 +5,12 @@
<field name="model">sql.file.wizard</field> <field name="model">sql.file.wizard</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Csv File"> <form string="Csv File">
<separator <field
string="variables_placeholder" name="query_properties"
colspan="4" nolabel="1"
invisible="1" columns="1"
hideKanbanOption="1"
required="1"
/> />
<separator <separator
string="Export file" string="Export file"
@ -17,6 +19,7 @@
/> />
<field name="binary_file" filename="file_name" /> <field name="binary_file" filename="file_name" />
<field name="file_name" invisible="1" /> <field name="file_name" invisible="1" />
<field name="sql_export_id" invisible="1" />
<footer> <footer>
<button <button
name="export_sql" name="export_sql"

View File

@ -118,10 +118,10 @@ class SqlExport(models.Model):
else: else:
export.send_mail() export.send_mail()
@api.constrains("field_ids", "mail_user_ids") @api.constrains("query_properties_definition", "mail_user_ids")
def check_no_parameter_if_sent_by_mail(self): def check_no_parameter_if_sent_by_mail(self):
for export in self: for export in self:
if export.field_ids and export.mail_user_ids: if export.query_properties_definition and export.mail_user_ids:
raise UserError( raise UserError(
_( _(
"It is not possible to execute and send a query " "It is not possible to execute and send a query "