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": [
"views/sql_export_view.xml",
"views/ir_model_fields_view.xml",
"wizard/wizard_file_view.xml",
"security/sql_export_security.xml",
"security/ir.model.access.csv",
],
"demo": [
"demo/ir_model_fields.xml",
"demo/sql_export.xml",
],
"assets": {
"web.assets_backend": [
"sql_export/static/src/scss/modal_properties.scss",
]
},
"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
LEFT JOIN res_partner_res_partner_category_rel rel
ON rel.partner_id = p.id
WHERE create_date &lt; %(x_date)s
AND id = %(x_id)s
AND rel.category_id in %(x_partner_categ_ids)s
WHERE create_date > %(Date)s
AND id = %(ID)s
AND rel.category_id in %(Categories)s
]]>
</field>
<field
eval="[(6, 0, [ref('date_field_variable_sql'), ref('integer_field_variable_sql'), ref('m2m_field_variable_sql')])]"
name="field_ids"
name="query_properties_definition"
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>

View File

@ -17,29 +17,8 @@ class SqlExport(models.Model):
file_format = fields.Selection([("csv", "CSV")], default="csv", required=True)
field_ids = fields.Many2many(
"ir.model.fields",
"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.",
)
use_properties = fields.Boolean(compute="_compute_use_properties")
query_properties_definition = fields.PropertiesDefinition("Query Properties")
encoding = fields.Selection(
[
@ -58,11 +37,27 @@ class SqlExport(models.Model):
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):
self.ensure_one()
wiz = self.env["sql.file.wizard"].create({"sql_export_id": self.id})
# no variable input, we can return the file directly
if not self.field_ids:
if not self.query_properties_definition:
return wiz.export_sql()
else:
return {
@ -88,3 +83,10 @@ class SqlExport(models.Model):
if self.encoding:
res = res.decode(self.encoding)
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.
There are some restrictions in the sql query, you can only read datas.
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
since variables features has been introduced. This can be fixed by
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
- `%(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
(Limitation for relational fields)
- for any created property, you can use it with `%(Property String)s` syntax

View File

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

View File

@ -4,7 +4,6 @@
import base64
from odoo import fields
from odoo.exceptions import UserError
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)
)
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.write({"state": "sql_valid"})
categ_id = self.env.ref("base.res_partner_category_0").id
wizard = self.wizard_obj.create(
{
"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()

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="arch" type="xml">
<field name="query" position="after">
<field name="use_properties" invisible="1" />
</field>
<button name="button_preview_sql_expression" position="attributes">
<attribute name="states" />
<attribute
name="attrs"
>{'invisible': [('field_ids', '!=', False)]}</attribute>
>{'invisible': [('use_properties', '=', True)]}</attribute>
</button>
<xpath expr="//header" position="inside">
<button
name="export_sql_query"
@ -24,6 +27,14 @@
class="oe_highlight"
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>
<group name="group_main_info" position="inside">
<group name="option">
@ -41,19 +52,14 @@
/>
</group>
</group>
<group name="group_query" position="after">
<group string="Parameters">
<field
name="field_ids"
nolabel="1"
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"
<field name="query" position="before">
<p
colspan="2"
attrs="{'invisible': [('use_properties', '=', False)]}"
> In case of use of properties in the query, use this syntax : &#37;&#37;(Property String)s. <br
/>
</group>
</group>
Example : SELECT id FROM sale_order WHERE create_date > &#37;&#37;(Start Date)s</p>
</field>
</field>
</record>

View File

@ -2,12 +2,10 @@
# @author: Florian da Costa
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import json
from datetime import datetime
from lxml import etree
from odoo import _, api, fields, models
from odoo import _, fields, models
from odoo.exceptions import UserError
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
@ -18,71 +16,34 @@ class SqlFileWizard(models.TransientModel):
binary_file = fields.Binary("File", readonly=True)
file_name = fields.Char(readonly=True)
sql_export_id = fields.Many2one(comodel_name="sql.export", required=True)
@api.model
def get_view(self, view_id=None, view_type="form", **options):
export_obj = self.env["sql.export"]
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
query_properties = fields.Properties(
string="Properties",
definition="sql_export_id.query_properties_definition",
copy=False,
)
def export_sql(self):
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
# Manage Params
variable_dict = {}
now_tz = fields.Datetime.context_timestamp(sql_export, datetime.now())
date = now_tz.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
if sql_export.field_ids:
for field in sql_export.field_ids:
if field.ttype == "many2one":
variable_dict[field.name] = self[field.name].id
elif field.ttype == "many2many":
variable_dict[field.name] = tuple(self[field.name].ids)
else:
variable_dict[field.name] = self[field.name]
for prop in self.query_properties:
if prop["type"] == "many2many":
variable_dict[prop["string"]] = tuple(prop["value"])
else:
variable_dict[prop["string"]] = prop["value"]
if "%(company_id)s" in sql_export.query:
company_id = self.env.company.id
variable_dict["company_id"] = company_id

View File

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

View File

@ -118,10 +118,10 @@ class SqlExport(models.Model):
else:
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):
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(
_(
"It is not possible to execute and send a query "