Browse Source

Add create and drop of SQL views

pull/245/head
Guewen Baconnier 10 years ago
parent
commit
5af43941ae
  1. 1
      sql_view/__init__.py
  2. 38
      sql_view/__openerp__.py
  3. 78
      sql_view/models/sql_view.py
  4. 3
      sql_view/security/ir.model.access.csv
  5. 24
      sql_view/views/sql_view_views.xml
  6. 3
      sql_view/wizards/__init__.py
  7. 82
      sql_view/wizards/sql_view_csv_preview.py
  8. 37
      sql_view/wizards/sql_view_csv_preview_views.xml

1
sql_view/__init__.py

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

38
sql_view/__openerp__.py

@ -33,31 +33,34 @@ SQL Views
This addon allows to create SQL views on the database. It also features This addon allows to create SQL views on the database. It also features
a simple CSV export of the views to check their result. a simple CSV export of the views to check their result.
Configuration
=============
To configure this module, you need to:
* go to ...
Usage Usage
===== =====
To use this module, you need to:
To create new SQL views, you need to go to ``Settings > Technical >
Database Structure > SQL Views``.
Give a view a human name, a SQL name (which will be prefixed with
``sql_view_`` in the database, and the definition of the view itself
(without trailing semicolon).
* go to ...
Known issues / Roadmap
======================
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/7.0
The CSV preview can be used to read any data on the database. So this
menu **must** be accessible only by allowed admin users. By
default, the module is configured to be accessible only by users having
the ``Settings`` administration level.
Bug Tracker Bug Tracker
=========== ===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback
`here <https://github.com/OCA/server-tools/issues/new?body=module:%20sql_view%0Aversion:%207.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
If you spotted it first, help us smashing it by providing a detailed and
welcomed feedback
`here
<https://github.com/OCA/server-tools/issues/new?body=module:%20sql_view%0Aversion:%207.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Credits Credits
======= =======
@ -83,7 +86,10 @@ promote its widespread use.
To contribute to this module, please visit http://odoo-community.org. To contribute to this module, please visit http://odoo-community.org.
""", """,
'website': 'http://www.camptocamp.com', 'website': 'http://www.camptocamp.com',
'data': ['views/sql_view_views.xml',
'external_dependencies': {'python': ['unicodecsv']},
'data': ['wizards/sql_view_csv_preview_views.xml',
'views/sql_view_views.xml',
'security/ir.model.access.csv',
], ],
'installable': True, 'installable': True,
} }

78
sql_view/models/sql_view.py

@ -20,7 +20,9 @@
# #
import re import re
import psycopg2
from openerp.osv import orm, fields from openerp.osv import orm, fields
from openerp.tools.translate import _
# views are created with a prefix to prevent conflicts # views are created with a prefix to prevent conflicts
SQL_VIEW_PREFIX = u'sql_view_' SQL_VIEW_PREFIX = u'sql_view_'
@ -30,13 +32,26 @@ USER_NAME_SIZE = 63 - len(SQL_VIEW_PREFIX)
PG_NAME_RE = re.compile(r'^[a-z_][a-z0-9_$]*$', re.I) PG_NAME_RE = re.compile(r'^[a-z_][a-z0-9_$]*$', re.I)
class sql_view(orm.Model):
class SQLView(orm.Model):
_name = 'sql.view' _name = 'sql.view'
def _compute_complete_sql_name(self, cr, uid, ids, name, args,
context=None):
res = {}
for sql_view in self.browse(cr, uid, ids, context=context):
res[sql_view.id] = SQL_VIEW_PREFIX + sql_view.sql_name
return res
_columns = { _columns = {
'name': fields.char(string='View Name', required=True), 'name': fields.char(string='View Name', required=True),
'sql_name': fields.char(string='SQL Name', required=True, 'sql_name': fields.char(string='SQL Name', required=True,
size=USER_NAME_SIZE),
size=USER_NAME_SIZE,
help="Name of the view. Will be prefixed "
"by %s" % (SQL_VIEW_PREFIX,)),
'complete_sql_name': fields.function(_compute_complete_sql_name,
string='Complete SQL Name',
readonly=True,
type='char'),
'definition': fields.text(string='Definition', required=True), 'definition': fields.text(string='Definition', required=True),
} }
@ -49,3 +64,62 @@ class sql_view(orm.Model):
'The SQL name is not a valid PostgreSQL identifier', 'The SQL name is not a valid PostgreSQL identifier',
['sql_name']), ['sql_name']),
] ]
def _sql_view_comment(self, cr, uid, sql_view, context=None):
return "%s (created by the module sql_view)" % sql_view.name
def _create_or_replace_sql_view(self, cr, uid, sql_view, context=None):
view_name = sql_view.complete_sql_name
try:
# The parenthesis around the definition are important:
# they prevent to insert a semicolon and another query after
# the view declaration
cr.execute(
"CREATE VIEW {view_name} AS ({definition})".format(
view_name=view_name,
definition=sql_view.definition)
)
except psycopg2.ProgrammingError as err:
raise orm.except_orm(
_('Error'),
_('The definition of the view is not correct:\n\n%s') % (err,)
)
comment = self._sql_view_comment(cr, uid, sql_view, context=context)
cr.execute(
"COMMENT ON VIEW {view_name} IS %s".format(view_name=view_name),
(comment,)
)
def _delete_sql_view(self, cr, uid, sql_view, context=None):
view_name = sql_view.complete_sql_name
cr.execute("DROP view IF EXISTS %s" % (view_name,))
def create(self, cr, uid, vals, context=None):
record_id = super(SQLView, self).create(cr, uid, vals,
context=context)
record = self.browse(cr, uid, record_id, context=context)
self._create_or_replace_sql_view(cr, uid, record, context=context)
return record_id
def write(self, cr, uid, ids, vals, context=None):
# If the name has been changed, we have to drop the view,
# it will be created with the new name.
# If the definition have been changed, we better have to delete
# it and create it again otherwise we can have 'cannot drop
# columns from view' errors.
for record in self.browse(cr, uid, ids, context=context):
self._delete_sql_view(cr, uid, record, context=context)
result = super(SQLView, self).write(cr, uid, ids, vals,
context=context)
for record in self.browse(cr, uid, ids, context=context):
self._create_or_replace_sql_view(cr, uid, record,
context=context)
return result
def unlink(self, cr, uid, ids, context=None):
for record in self.browse(cr, uid, ids, context=context):
self._delete_sql_view(cr, uid, record, context=context)
result = super(SQLView, self).unlink(cr, uid, ids, context=context)
return result

3
sql_view/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_sql_view_admin,sql_view admin,model_sql_view,base.group_system,1,1,1,1

24
sql_view/views/sql_view_views.xml

@ -5,12 +5,24 @@
<record id="view_sql_view_form" model="ir.ui.view"> <record id="view_sql_view_form" model="ir.ui.view">
<field name="name">sql.view.form</field> <field name="name">sql.view.form</field>
<field name="model">sql.view</field> <field name="model">sql.view</field>
<field name="type">form</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="SQL Views">
<form string="SQL View" version="7.0">
<header>
<button name="%(action_sql_view_csv_preview)d"
type="action"
class="oe_highlight"
string="CSV Preview"/>
</header>
<sheet string="SQL View">
<group>
<field name="name"/> <field name="name"/>
<field name="sql_name"/> <field name="sql_name"/>
<field name="definition" colspan="4"/>
<field name="complete_sql_name"/>
</group>
<group string="Definition">
<field name="definition" nolabel="1"/>
</group>
</sheet>
</form> </form>
</field> </field>
</record> </record>
@ -18,11 +30,10 @@
<record id="view_sql_view_tree" model="ir.ui.view"> <record id="view_sql_view_tree" model="ir.ui.view">
<field name="name">sql.view.tree</field> <field name="name">sql.view.tree</field>
<field name="model">sql.view</field> <field name="model">sql.view</field>
<field name="type">tree</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="SQL Views"> <tree string="SQL Views">
<field name="name"/> <field name="name"/>
<field name="sql_name"/>
<field name="complete_sql_name"/>
</tree> </tree>
</field> </field>
</record> </record>
@ -30,11 +41,10 @@
<record id="view_sql_view_search" model="ir.ui.view"> <record id="view_sql_view_search" model="ir.ui.view">
<field name="name">sql.view.filter</field> <field name="name">sql.view.filter</field>
<field name="model">sql.view</field> <field name="model">sql.view</field>
<field name="type">search</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="SQL Views"> <search string="SQL Views">
<field name="name"/> <field name="name"/>
<field name="sql_name"/>
<field name="complete_sql_name"/>
</search> </search>
</field> </field>
</record> </record>

3
sql_view/wizards/__init__.py

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

82
sql_view/wizards/sql_view_csv_preview.py

@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
#
#
# Authors: Guewen Baconnier
# Copyright 2015 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
import base64
from StringIO import StringIO
import unicodecsv
from openerp.osv import orm, fields
class SQLViewCSVPreview(orm.TransientModel):
_name = 'sql.view.csv.preview'
_columns = {
'limit': fields.integer(string='Limit',
help='Number of records. 0 means infinite.'),
'data': fields.binary('CSV', readonly=True),
'filename': fields.char('File Name', readonly=True),
}
_defaults = {
'filename': 'csv-preview.csv',
'limit': 100,
}
def _query(self, cr, uid, form, sql_view, context=None):
view_name = sql_view.complete_sql_name
query = "SELECT * FROM {view_name} "
if form.limit:
query += "LIMIT {limit}"
return query.format(view_name=view_name, limit=form.limit)
def export_csv(self, cr, uid, ids, context=None):
if context is None:
return
sql_view_ids = context.get('active_ids', [])
assert len(ids) == 1, "1 wizard ID expected"
assert len(sql_view_ids) == 1, "1 active ID expected"
form = self.browse(cr, uid, ids[0], context=context)
sql_view = self.pool['sql.view'].browse(cr, uid, sql_view_ids[0],
context=context)
query = self._query(cr, uid, form, sql_view, context=context)
cr.execute(query)
headers = [desc[0] for desc in cr.description]
records = cr.fetchall()
filedata = StringIO()
try:
writer = unicodecsv.writer(filedata, encoding='utf-8')
writer.writerow(headers)
writer.writerows(records)
form.write({'data': base64.encodestring(filedata.getvalue())})
finally:
filedata.close()
return {
'type': 'ir.actions.act_window',
'res_model': self._name,
'view_mode': 'form',
'view_type': 'form',
'res_id': ids[0],
'views': [(False, 'form')],
'target': 'new',
}

37
sql_view/wizards/sql_view_csv_preview_views.xml

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record id="view_sql_view_csv_preview_form" model="ir.ui.view">
<field name="name">sql.view.csv.preview.form</field>
<field name="model">sql.view.csv.preview</field>
<field name="arch" type="xml">
<form string="SQL View CSV Preview" version="7.0">
<group>
<field name="limit" attrs="{'invisible': [('data', '!=', False)]}"/>
<field name="filename" invisible="1"/>
<field name="data" attrs="{'invisible': [('data', '=', False)]}"
filename="filename"/>
</group>
<footer attrs="{'invisible': [('data', '!=', False)]}">
<button string="Preview" name="export_csv" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>
<footer attrs="{'invisible': [('data', '=', False)]}">
<button special="cancel" string="Close" type="object"/>
</footer>
</form>
</field>
</record>
<record id="action_sql_view_csv_preview" model="ir.actions.act_window">
<field name="name">SQL View CSV Preview</field>
<field name="res_model">sql.view.csv.preview</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_sql_view_csv_preview_form"/>
<field name="target">new</field>
</record>
</data>
</openerp>
Loading…
Cancel
Save