Browse Source
[ADD] base_export_security: Create module (#917)
[ADD] base_export_security: Create module (#917)
* [ADD] base_export_security: Create module * Add Export Rights security group, access rights * Add user rights checks to prevent unauthorized exports and hide Export menu item from unauthorized users * Add Export model for logging data export activity * Add views, menu link for Export Logs * Add data exports discussion channel, notifications of exports * [IMP] base_export_security: Make requested changes * Fix manifest website and depends * Fix xml structure * Rename model 'export' -> 'export.event' * Simplify date calculation * Use % instead of .format * Avoid translating variables * Assign recordsets to variables instead of ids * Use (6, _, ids) command instead of (4, id, _) * Clean up syntax redundancies * Remove unnecessary sort * Override export_data method with inheritance/super * [FIX] base_export_security: Fix eslint errors * Fix prefer-rest-params * Fix no-prototype-builtins * Fix comma-dangle * Fix dot-location * Fix unnecessary apply * [IMP] base_export_security: Update readme * Add three screenshots from PR to readmepull/1029/merge
Brenton Hughes
7 years ago
committed by
Dave Lasley
17 changed files with 586 additions and 0 deletions
-
92base_export_security/README.rst
-
5base_export_security/__init__.py
-
25base_export_security/__manifest__.py
-
12base_export_security/data/export.xml
-
6base_export_security/models/__init__.py
-
26base_export_security/models/base.py
-
81base_export_security/models/export.py
-
13base_export_security/security/export_security.xml
-
2base_export_security/security/ir.model.access.csv
-
BINbase_export_security/static/description/export_log.png
-
BINbase_export_security/static/description/export_menu_item_hidden.png
-
BINbase_export_security/static/description/export_notification.png
-
35base_export_security/static/src/js/base_export_security.js
-
71base_export_security/static/tests/js/base_export_security.js
-
5base_export_security/tests/__init__.py
-
123base_export_security/tests/test_export.py
-
90base_export_security/views/export_view.xml
@ -0,0 +1,92 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg |
||||
|
:target: http://www.gnu.org/licenses/lgpl |
||||
|
:alt: License: LGPL-3 |
||||
|
|
||||
|
=============== |
||||
|
Export Security |
||||
|
=============== |
||||
|
|
||||
|
This module adds the following security features to Odoo's data exporting: |
||||
|
|
||||
|
#. A security group for restricting users' ability to export data |
||||
|
#. A log of exports detailing the user responsible, as well as the model, records, and fields exported |
||||
|
#. A `#data exports` channel for export activity notifications |
||||
|
|
||||
|
Screenshots |
||||
|
=========== |
||||
|
|
||||
|
Hidden Export Menu Item |
||||
|
----------------------- |
||||
|
|
||||
|
.. image:: /base_export_security/static/description/export_menu_item_hidden.png?raw=true |
||||
|
:alt: Hidden Export Menu Item |
||||
|
:width: 600 px |
||||
|
|
||||
|
Export Log |
||||
|
---------- |
||||
|
|
||||
|
.. image:: /base_export_security/static/description/export_log.png?raw=true |
||||
|
:alt: Export Log |
||||
|
:width: 600 px |
||||
|
|
||||
|
Channel Notification |
||||
|
-------------------- |
||||
|
|
||||
|
.. image:: /base_export_security/static/description/export_notification.png?raw=true |
||||
|
:alt: Export Notification |
||||
|
:width: 500 px |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
To configure this module, you need to: |
||||
|
|
||||
|
#. Add users who require data export rights to the `Export Rights` security group, found in Settings/Users (requires developer mode to be active) |
||||
|
#. Invite users who should receive export activity notifications to the `#data exports` notification channel |
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
To view detailed Export Logs, go to Settings/Data Exports/Export Logs |
||||
|
|
||||
|
.. 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/10.0 |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
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. If you spotted it first, |
||||
|
help us smash it by providing detailed and welcomed feedback. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Images |
||||
|
------ |
||||
|
|
||||
|
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Brent Hughes <brent.hughes@laslabs.com> |
||||
|
|
||||
|
Do not contact contributors directly about support or help with technical issues. |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
To contribute to this module, please visit https://odoo-community.org. |
@ -0,0 +1,5 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import models |
@ -0,0 +1,25 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
{ |
||||
|
"name": "Export Security", |
||||
|
"summary": "Security features for Odoo exports", |
||||
|
"version": "10.0.1.0.0", |
||||
|
"category": "Extra Tools", |
||||
|
"website": "https://laslabs.com/", |
||||
|
"author": "LasLabs, Odoo Community Association (OCA)", |
||||
|
"license": "LGPL-3", |
||||
|
"application": False, |
||||
|
"installable": True, |
||||
|
"depends": [ |
||||
|
"mail", |
||||
|
"web", |
||||
|
], |
||||
|
"data": [ |
||||
|
"data/export.xml", |
||||
|
"security/export_security.xml", |
||||
|
"security/ir.model.access.csv", |
||||
|
"views/export_view.xml", |
||||
|
], |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2017 LasLabs Inc. |
||||
|
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). --> |
||||
|
|
||||
|
<odoo noupdate="1"> |
||||
|
<record id="export_channel" model="mail.channel"> |
||||
|
<field name="name">data exports</field> |
||||
|
<field name="public">private</field> |
||||
|
<field name="channel_partner_ids" eval="[(4, ref('base.partner_root'))]"/> |
||||
|
<field name="description">Notifications of data export activity</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,6 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import base |
||||
|
from . import export |
@ -0,0 +1,26 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from odoo import _, api, models |
||||
|
from odoo.exceptions import AccessError |
||||
|
|
||||
|
|
||||
|
class Base(models.AbstractModel): |
||||
|
_inherit = 'base' |
||||
|
|
||||
|
@api.multi |
||||
|
def export_data(self, fields_to_export, raw_data=False): |
||||
|
if self.env.user.has_group('base_export_security.export_group'): |
||||
|
field_names = map( |
||||
|
lambda path_array: path_array[0], map( |
||||
|
models.fix_import_export_id_paths, |
||||
|
fields_to_export, |
||||
|
), |
||||
|
) |
||||
|
self.env['export.event'].log_export(self, field_names) |
||||
|
return super(Base, self).export_data(fields_to_export, raw_data) |
||||
|
else: |
||||
|
raise AccessError( |
||||
|
_('You do not have permission to export data'), |
||||
|
) |
@ -0,0 +1,81 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from odoo import _, api, fields, models |
||||
|
|
||||
|
|
||||
|
class Export(models.Model): |
||||
|
_name = 'export.event' |
||||
|
_description = 'Data Export Record' |
||||
|
|
||||
|
name = fields.Char() |
||||
|
model_id = fields.Many2one( |
||||
|
'ir.model', |
||||
|
string='Exported Model', |
||||
|
readonly=True, |
||||
|
) |
||||
|
user_id = fields.Many2one( |
||||
|
'res.users', |
||||
|
string='Exported by', |
||||
|
readonly=True, |
||||
|
) |
||||
|
field_ids = fields.Many2many( |
||||
|
'ir.model.fields', |
||||
|
string='Exported Fields', |
||||
|
readonly=True, |
||||
|
) |
||||
|
record_ids = fields.Many2many( |
||||
|
'ir.model.data', |
||||
|
string='Exported Records', |
||||
|
readonly=True, |
||||
|
) |
||||
|
|
||||
|
@api.model |
||||
|
def log_export(self, recordset, field_names): |
||||
|
date = fields.Datetime.now() |
||||
|
model_name = recordset._name |
||||
|
model = self.env['ir.model'].search([('model', '=', model_name)]) |
||||
|
user = self.env.user |
||||
|
name_data = {'date': date, 'model': model.name, 'user': user.name} |
||||
|
name = '%(date)s / %(model)s / %(user)s' % name_data |
||||
|
exported_fields = self.env['ir.model.fields'].search([ |
||||
|
('model', '=', model_name), |
||||
|
('name', 'in', field_names), |
||||
|
]) |
||||
|
records = self.env['ir.model.data'].search([ |
||||
|
('model', '=', model_name), |
||||
|
('res_id', 'in', recordset.ids), |
||||
|
]) |
||||
|
export = self.create({ |
||||
|
'name': name, |
||||
|
'model_id': model.id, |
||||
|
'field_ids': [(6, 0, exported_fields.ids)], |
||||
|
'record_ids': [(6, 0, records.ids)], |
||||
|
'user_id': user.id, |
||||
|
}) |
||||
|
export.sudo().post_notification() |
||||
|
return export |
||||
|
|
||||
|
@api.multi |
||||
|
def post_notification(self): |
||||
|
channel = self.env.ref('base_export_security.export_channel') |
||||
|
field_labels = ', '.join( |
||||
|
self.field_ids.mapped('field_description'), |
||||
|
) |
||||
|
message_data = { |
||||
|
'records': len(self.record_ids), |
||||
|
'model': self.model_id.name, |
||||
|
'user': self.user_id.name, |
||||
|
'fields': field_labels, |
||||
|
} |
||||
|
message_body = _( |
||||
|
'%(records)d <b>%(model)s</b> records exported by <b>%(user)s' |
||||
|
'</b>.<br><b>Fields exported:</b> %(fields)s' |
||||
|
) % message_data |
||||
|
message = channel.message_post( |
||||
|
body=message_body, |
||||
|
message_type='notification', |
||||
|
subtype='mail.mt_comment', |
||||
|
) |
||||
|
return message |
@ -0,0 +1,13 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2017 LasLabs Inc. |
||||
|
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). --> |
||||
|
|
||||
|
<odoo> |
||||
|
<record id="export_group" model="res.groups"> |
||||
|
<field name="name">Export Rights</field> |
||||
|
<field name="comment"> |
||||
|
The user will be able to export data. |
||||
|
</field> |
||||
|
<field name="users" eval="[(4, ref('base.user_root'))]"/> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,2 @@ |
|||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink |
||||
|
export_rights,export rights,model_export_event,base_export_security.export_group,1,1,1,1 |
After Width: 1000 | Height: 655 | Size: 263 KiB |
After Width: 1000 | Height: 297 | Size: 100 KiB |
After Width: 1000 | Height: 137 | Size: 95 KiB |
@ -0,0 +1,35 @@ |
|||||
|
/* Copyright 2017 LasLabs Inc. |
||||
|
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). */
|
||||
|
|
||||
|
odoo.define('base_export_security', function(require){ |
||||
|
'use strict'; |
||||
|
|
||||
|
var Model = require('web.Model'); |
||||
|
var ListView = require('web.ListView'); |
||||
|
var core = require('web.core'); |
||||
|
|
||||
|
ListView.include({ |
||||
|
render_sidebar: function($node) { |
||||
|
var exportLabel = core._t('Export'); |
||||
|
var users = new Model('res.users'); |
||||
|
var res = this._super($node); |
||||
|
|
||||
|
if (this.sidebar && Object.prototype.hasOwnProperty.call(this.sidebar.items, 'other')) { |
||||
|
users.call('has_group', ['base_export_security.export_group']).then(function(result){ |
||||
|
if(!result){ |
||||
|
var filteredItems = this.sidebar.items.other.filter( |
||||
|
function(item){ |
||||
|
return item.label !== exportLabel; |
||||
|
} |
||||
|
); |
||||
|
this.sidebar.items.other = filteredItems; |
||||
|
this.sidebar.redraw(); |
||||
|
} |
||||
|
}.bind(this)); |
||||
|
} |
||||
|
|
||||
|
return res; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
}); |
@ -0,0 +1,71 @@ |
|||||
|
/* Copyright 2017 LasLabs Inc. |
||||
|
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). */
|
||||
|
|
||||
|
odoo.define_section('base_export_security', |
||||
|
['web.core', 'web.data', 'web.data_manager', 'web.ListView'], |
||||
|
function(test, mock) { |
||||
|
'use strict'; |
||||
|
|
||||
|
function setup (authorization) { |
||||
|
mock.add('test.model:read', function () { |
||||
|
return [{ id: 1, a: 'foo', b: 'bar', c: 'baz' }]; |
||||
|
}); |
||||
|
|
||||
|
mock.add('res.users:has_group', function () { |
||||
|
return authorization; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function fields_view_get () { |
||||
|
return { |
||||
|
type: 'tree', |
||||
|
fields: { |
||||
|
a: {type: 'char', string: 'A'}, |
||||
|
b: {type: 'char', string: 'B'}, |
||||
|
c: {type: 'char', string: 'C'} |
||||
|
}, |
||||
|
arch: '<tree><field name="a"/><field name="b"/><field name="c"/></tree>' |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function labelCount (exportLabel) { |
||||
|
var $fix = $('#qunit-fixture'); |
||||
|
var exportItems = $fix.find('.btn-group a[data-section="other"]').filter( |
||||
|
function(i, item){ |
||||
|
return item.text.trim() === exportLabel; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
return exportItems.length; |
||||
|
} |
||||
|
|
||||
|
function renderView (data, data_manager, ListView) { |
||||
|
var $fix = $('#qunit-fixture'); |
||||
|
var dataset = new data.DataSetStatic(null, 'test.model', null, [1]); |
||||
|
var fields_view = data_manager._postprocess_fvg(fields_view_get()); |
||||
|
var listView = new ListView({}, dataset, fields_view, {sidebar: true}); |
||||
|
|
||||
|
listView.appendTo($fix).then(listView.render_sidebar($fix)); |
||||
|
} |
||||
|
|
||||
|
test('It should display the Export menu item to authorized users', |
||||
|
function(assert, core, data, data_manager, ListView) { |
||||
|
var exportLabel = core._t('Export'); |
||||
|
setup(true); |
||||
|
renderView(data, data_manager, ListView); |
||||
|
|
||||
|
assert.strictEqual(labelCount(exportLabel), 1); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should not display the Export menu item to unauthorized users', |
||||
|
function(assert, core, data, data_manager, ListView) { |
||||
|
var exportLabel = core._t('Export'); |
||||
|
setup(false); |
||||
|
renderView(data, data_manager, ListView); |
||||
|
|
||||
|
assert.strictEqual(labelCount(exportLabel), 0); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
}); |
@ -0,0 +1,5 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import test_export |
@ -0,0 +1,123 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
import mock |
||||
|
|
||||
|
from odoo import _ |
||||
|
from odoo.exceptions import AccessError |
||||
|
from odoo.tests.common import HttpCase, TransactionCase |
||||
|
|
||||
|
|
||||
|
class TestExport(TransactionCase): |
||||
|
def setUp(self): |
||||
|
super(TestExport, self).setUp() |
||||
|
export_group = self.env.ref('base_export_security.export_group') |
||||
|
self.authorized_user = self.env['res.users'].create({ |
||||
|
'login': 'exporttestuser', |
||||
|
'partner_id': self.env['res.partner'].create({ |
||||
|
'name': "Export Test User" |
||||
|
}).id, |
||||
|
'groups_id': [(4, export_group.id, 0)], |
||||
|
}) |
||||
|
self.unauthorized_user = self.env['res.users'].create({ |
||||
|
'login': 'unauthorizedexporttestuser', |
||||
|
'partner_id': self.env['res.partner'].create({ |
||||
|
'name': "Unauthorized Export Test User" |
||||
|
}).id, |
||||
|
}) |
||||
|
self.model_name = 'ir.module.module' |
||||
|
self.recordset = self.env[self.model_name].search([]) |
||||
|
self.field_names = ['name', 'id'] |
||||
|
self.model = self.env['ir.model'].search([ |
||||
|
('model', '=', self.model_name), |
||||
|
]) |
||||
|
self.records = self.env['ir.model.data'].search([ |
||||
|
('model', '=', self.model_name), |
||||
|
('res_id', 'in', self.recordset.ids), |
||||
|
]) |
||||
|
self.fields = self.env['ir.model.fields'].search([ |
||||
|
('model', '=', self.model_name), |
||||
|
('name', 'in', self.field_names), |
||||
|
]) |
||||
|
|
||||
|
def test_log_export(self): |
||||
|
"""It should create a new Export record with correct data""" |
||||
|
log = self.env['export.event'].sudo(self.authorized_user).log_export( |
||||
|
self.recordset, |
||||
|
self.field_names, |
||||
|
) |
||||
|
self.assertEqual( |
||||
|
[log.model_id, log.field_ids, log.record_ids, log.user_id], |
||||
|
[self.model, self.fields, self.records, self.authorized_user], |
||||
|
'Log not created properly', |
||||
|
) |
||||
|
|
||||
|
def test_log_export_posts_notification(self): |
||||
|
"""It should call post_notification method""" |
||||
|
post_notification_mock = mock.MagicMock() |
||||
|
self.env['export.event']._patch_method( |
||||
|
'post_notification', |
||||
|
post_notification_mock, |
||||
|
) |
||||
|
self.env['export.event'].sudo(self.authorized_user).log_export( |
||||
|
self.recordset, |
||||
|
self.field_names, |
||||
|
) |
||||
|
post_notification_mock.assert_called_once_with() |
||||
|
self.env['export.event']._revert_method('post_notification') |
||||
|
|
||||
|
def test_post_notification(self): |
||||
|
"""It should post a notification with appropriate data |
||||
|
to the #data export channel""" |
||||
|
export = self.env['export.event'].create({ |
||||
|
'model_id': self.model.id, |
||||
|
'field_ids': [(4, self.fields.ids)], |
||||
|
'record_ids': [(4, self.records.ids)], |
||||
|
'user_id': self.authorized_user.id, |
||||
|
}) |
||||
|
message = export.sudo().post_notification() |
||||
|
field_labels = ', '.join( |
||||
|
self.fields.sorted().mapped('field_description'), |
||||
|
) |
||||
|
message_data = { |
||||
|
'records': len(self.records.ids), |
||||
|
'model': self.model.name, |
||||
|
'user': self.authorized_user.name, |
||||
|
'fields': field_labels, |
||||
|
} |
||||
|
message_body = _( |
||||
|
'<p>%(records)d <b>%(model)s</b> records exported by <b>%(user)s' |
||||
|
'</b>.<br><b>Fields exported:</b> %(fields)s</p>' |
||||
|
) % message_data |
||||
|
self.assertEqual( |
||||
|
[message.body, message.message_type, message.model], |
||||
|
[message_body, 'notification', 'mail.channel'], |
||||
|
'Message not posted properly', |
||||
|
) |
||||
|
|
||||
|
def test_export_data_access(self): |
||||
|
"""It should raise AccessError if user does not have export rights""" |
||||
|
with self.assertRaises(AccessError): |
||||
|
self.env[self.model_name].sudo( |
||||
|
self.unauthorized_user |
||||
|
).export_data(self, None) |
||||
|
|
||||
|
def test_export_data_calls_log_export(self): |
||||
|
"""It should call log_export if user has export rights""" |
||||
|
log_export_mock = mock.MagicMock() |
||||
|
self.env['export.event']._patch_method('log_export', log_export_mock) |
||||
|
model = self.env[self.model_name] |
||||
|
model.sudo(self.authorized_user).export_data(self.field_names) |
||||
|
log_export_mock.assert_called_once_with(model, self.field_names) |
||||
|
self.env['export.event']._revert_method('log_export') |
||||
|
|
||||
|
|
||||
|
class TestJS(HttpCase): |
||||
|
def test_export_visibility(self): |
||||
|
"""Test visibility of export menu item""" |
||||
|
self.phantom_js( |
||||
|
"/web/tests?debug=assets&module=base_export_security", |
||||
|
"", |
||||
|
login="admin", |
||||
|
) |
@ -0,0 +1,90 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2017 LasLabs Inc. |
||||
|
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). --> |
||||
|
|
||||
|
<odoo> |
||||
|
<record id="export_view_form" model="ir.ui.view"> |
||||
|
<field name="name">Export Log</field> |
||||
|
<field name="model">export.event</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="Export Log" create="false"> |
||||
|
<sheet> |
||||
|
<group> |
||||
|
<field name="name"/> |
||||
|
<field name="model_id"/> |
||||
|
<field name="user_id"/> |
||||
|
<field name="create_date" |
||||
|
string="Export Date" |
||||
|
readonly="1"/> |
||||
|
</group> |
||||
|
<notebook> |
||||
|
<page string="Exported Fields"> |
||||
|
<field name="field_ids"/> |
||||
|
</page> |
||||
|
<page string="Exported Records"> |
||||
|
<field name="record_ids"/> |
||||
|
</page> |
||||
|
</notebook> |
||||
|
</sheet> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="export_view_tree" model="ir.ui.view"> |
||||
|
<field name="name">Export Logs</field> |
||||
|
<field name="model">export.event</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree string="Export Logs" create="false" default_order='create_date desc'> |
||||
|
<field name="model_id"/> |
||||
|
<field name="user_id"/> |
||||
|
<field name="create_date" string="Export Date"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="export_view_search" model="ir.ui.view"> |
||||
|
<field name="name">Export Logs Search</field> |
||||
|
<field name="model">export.event</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<search string="Export Logs"> |
||||
|
<field name="user_id"/> |
||||
|
<field name="create_date" string="Export Date"/> |
||||
|
<field name="model_id"/> |
||||
|
<field name="field_ids"/> |
||||
|
<field name="record_ids"/> |
||||
|
</search> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="export_action_log" model="ir.actions.act_window"> |
||||
|
<field name="name">Export Logs</field> |
||||
|
<field name="res_model">export.event</field> |
||||
|
<field name="view_type">form</field> |
||||
|
<field name="view_mode">tree,form</field> |
||||
|
<field name="search_view_id" ref="export_view_search"/> |
||||
|
</record> |
||||
|
|
||||
|
<menuitem id="export_category" |
||||
|
name="Data Exports" |
||||
|
parent="base.menu_administration"/> |
||||
|
|
||||
|
<menuitem id="export_menu" |
||||
|
name="Export Logs" |
||||
|
sequence="7" |
||||
|
parent="export_category" |
||||
|
action="export_action_log"/> |
||||
|
|
||||
|
<template id="assets_backend" inherit_id="web.assets_backend"> |
||||
|
<xpath expr="."> |
||||
|
<script type="text/javascript" |
||||
|
src="/base_export_security/static/src/js/base_export_security.js"/> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
|
||||
|
<template id="qunit_suite" inherit_id="web.qunit_suite"> |
||||
|
<xpath expr="//t[@t-set='head']" position="inside"> |
||||
|
<script type="application/javascript" |
||||
|
src="/base_export_security/static/tests/js/base_export_security.js"/> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue