Browse Source

[ADD] user_threshold: Create module (#836)

* [ADD] user_threshold: Create module

* [IMP] user_threshold: Fixes per PR
* Fix README
* Rename poorly named methods
* Syntactic sugar
* Remove direct SQL in favor of ORM API
* Docstrings

* [IMP] user_threshold: Fixes per PR
* Update verbiage in README
* Simplify access check calls
* Use the same verbiage throughout error messages
* Add Link to related modules in README
pull/863/head
Ted Salmon 8 years ago
committed by Dave Lasley
parent
commit
1fea55934f
  1. 95
      user_threshold/README.rst
  2. 5
      user_threshold/__init__.py
  3. 20
      user_threshold/__manifest__.py
  4. 14
      user_threshold/data/ir_config_parameter_data.xml
  5. 13
      user_threshold/data/user_threshold_data.xml
  6. 9
      user_threshold/models/__init__.py
  7. 45
      user_threshold/models/ir_config_parameter.py
  8. 47
      user_threshold/models/res_company.py
  9. 27
      user_threshold/models/res_groups.py
  10. 130
      user_threshold/models/res_users.py
  11. 8
      user_threshold/tests/__init__.py
  12. 31
      user_threshold/tests/common.py
  13. 87
      user_threshold/tests/test_ir_config_parameter.py
  14. 44
      user_threshold/tests/test_res_company.py
  15. 34
      user_threshold/tests/test_res_groups.py
  16. 127
      user_threshold/tests/test_res_users.py
  17. 19
      user_threshold/views/res_company_view.xml
  18. 22
      user_threshold/views/res_users_view.xml

95
user_threshold/README.rst

@ -0,0 +1,95 @@
.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
==============
User Threshold
==============
This module adds the ability to limit the amount of non-portal/public
users that exist in the database and per-company.
It adds a group named `User Threshold Managers` which are the only users
who can alter the thresholds.
This module also limits the ability of users to add membership
to the manager group to pre-existing members. By default, `Administrator`
is the only member of this group.
Additionally, there is a flag that can be set on users so that they do not
count towards the user threshold.
Using the `USER_THRESHOLD_HIDE` environment variable, you can also hide the
threshold exemption flag from users and the company setting for user
threshold. Setting this flag will also remove threshold exemptions for any
users who are not defined in the `USER_THRESHOLD_USER` environment variable.
There are two modules available that also implement functionality similar to
what is provided in this module but in a more abstract way. They are:
https://github.com/it-projects-llc/access-addons/tree/10.0/access_limit_records_number
https://github.com/it-projects-llc/access-addons/tree/10.0/access_restricted
Usage
=====
A system parameter named `user.threshold.database` is added by default with
the value of '0' (Unlimited). Set this value to the total number of users
you wish to allow in the database.
A field has been added to users to allow you to exempt them from the
thresholds.
A field has been added to all companies, which allows you to define the max
number of users that the company can have.
The following environment variables are available for your configuration ease:
| Name | Description |
|------|-------------|
| USER_THRESHOLD_HIDE | Hide all threshold settings and default the exempt users to those defined by the USER_THRESHOLD_USERS variable
| USER_THRESHOLD_USER | White list of users who are exempt from the threshold.
.. 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
------------
* Ted Salmon <tsalmon@laslabs.com>
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.

5
user_threshold/__init__.py

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

20
user_threshold/__manifest__.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
{
"name": "User Threshold",
"summary": "Add Configurable User Threshold Support",
"version": "10.0.1.0.0",
"category": "Authentication",
"website": "https://www.laslabs.com",
"author": "LasLabs, Odoo Community Association (OCA)",
"license": "LGPL-3",
"application": False,
'installable': True,
"data": [
'data/user_threshold_data.xml',
'data/ir_config_parameter_data.xml',
'views/res_company_view.xml',
'views/res_users_view.xml',
],
}

14
user_threshold/data/ir_config_parameter_data.xml

@ -0,0 +1,14 @@
<?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="config_user_threshold" model="ir.config_parameter">
<field name="key">user.threshold.database</field>
<field name="value">0</field>
<field name="group_ids"
eval="[(4, ref('user_threshold.group_threshold_manager'))]" />
</record>
</odoo>

13
user_threshold/data/user_threshold_data.xml

@ -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="group_threshold_manager" model="res.groups">
<field name="name">User Threshold Manager</field>
<field name="category_id" ref="base.module_category_hidden"/>
<field name="users" eval="[(4, ref('base.user_root'))]" />
</record>
</odoo>

9
user_threshold/models/__init__.py

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from . import ir_config_parameter
from . import res_company
from . import res_groups
from . import res_users

45
user_threshold/models/ir_config_parameter.py

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import os
from odoo import _, api, models
from odoo.exceptions import AccessError
from .res_groups import THRESHOLD_MANAGER
MAX_DB_USER_PARAM = 'user.threshold.database'
THRESHOLD_HIDE = str(os.environ.get('USER_THRESHOLD_HIDE', '')) == '1'
class IrConfigParameter(models.Model):
_inherit = 'ir.config_parameter'
@api.multi
def unlink(self):
"""
Override to disallow deletion of the user threshold parameter
when the user does not have the right access
"""
for rec in self.filtered(lambda r: r.key == MAX_DB_USER_PARAM):
if not self.env.user.has_group(THRESHOLD_MANAGER):
raise AccessError(_(
'You must be a member of the `User Threshold Manager` '
'to delete this parameter'
))
return super(IrConfigParameter, self).unlink()
@api.multi
def write(self, vals):
"""
Override to disallow manipulation of the user threshold parameter
when the user does not have the right access
"""
for rec in self.filtered(lambda r: r.key == MAX_DB_USER_PARAM):
if not self.env.user.has_group(THRESHOLD_MANAGER):
raise AccessError(_(
'You must be a member of the `User Threshold Manager` '
'to set this parameter'
))
return super(IrConfigParameter, self).write(vals)

47
user_threshold/models/res_company.py

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from lxml import etree
from odoo import _, api, fields, models
from odoo.exceptions import AccessError
from .ir_config_parameter import THRESHOLD_HIDE
from .res_groups import THRESHOLD_MANAGER
class ResCompany(models.Model):
_inherit = 'res.company'
max_users = fields.Integer(
'Maximum Number of users allowed for this company',
)
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
submenu=False):
""" Hide Max User Field when the env var to hide the field is set """
res = super(ResCompany, self).fields_view_get(
view_id, view_type, toolbar, submenu
)
if THRESHOLD_HIDE:
doc = etree.XML(res['arch'])
for node in doc.xpath("//field[@name='max_users']"):
node.getparent().remove(node)
res['arch'] = etree.tostring(doc, pretty_print=True)
return res
@api.multi
def write(self, vals):
"""
Override to disallow manipulation of the user threshold parameter
when the user does not have the right access
"""
is_manager = self.env.user.has_group(THRESHOLD_MANAGER)
if vals.get('max_users') and not is_manager:
raise AccessError(_(
'You must be a member of the `User Threshold Manager` to set '
'this parameter'
))
return super(ResCompany, self).write(vals)

27
user_threshold/models/res_groups.py

@ -0,0 +1,27 @@
# -*- 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
THRESHOLD_MANAGER = 'user_threshold.group_threshold_manager'
class ResGroups(models.Model):
_inherit = 'res.groups'
@api.multi
def write(self, vals):
""" Override write to verify that membership of the Threshold Manager
group is not able to be set by users outside that group
"""
manager = self.env.ref(THRESHOLD_MANAGER, raise_if_not_found=False)
if manager:
is_manager = self.env.user.has_group(THRESHOLD_MANAGER)
if not is_manager and manager in self:
raise AccessError(_(
'You must be a member of the `User Threshold Manager` '
'group to grant access to it.'
))
return super(ResGroups, self).write(vals)

130
user_threshold/models/res_users.py

@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import os
from csv import reader
from lxml import etree
from odoo import SUPERUSER_ID, _, api, fields, models, registry
from odoo.exceptions import AccessError, ValidationError
from .ir_config_parameter import THRESHOLD_HIDE, MAX_DB_USER_PARAM
from .res_groups import THRESHOLD_MANAGER
class ResUsers(models.Model):
_inherit = 'res.users'
threshold_exempt = fields.Boolean(
'Exempt User From User Count Thresholds',
)
def __init__(self, pool, cr):
"""
Override to check if env var to hide threshold configuration and
reset the database state is set. If it is, run those actions
"""
if THRESHOLD_HIDE:
exempt_users_var = os.environ.get('USER_THRESHOLD_USER', '')
exempt_users = reader([exempt_users_var])
with api.Environment.manage():
with registry(cr.dbname).cursor() as new_cr:
new_env = api.Environment(new_cr, SUPERUSER_ID, {})
installed = new_env['ir.module.module'].search_count([
('name', '=', 'user_threshold'),
('state', '=', 'installed'),
])
if installed:
users = new_env['res.users'].search([
('share', '=', False),
('threshold_exempt', '=', True),
])
non_ex = users.filtered(
lambda r: r.login not in exempt_users
)
for user in non_ex:
user.threshold_exempt = False
new_cr.commit()
def _check_thresholds(self):
"""
Check to see if any user thresholds are met
Returns:
False when the thresholds aren't met and True when they are
"""
domain = [
('threshold_exempt', '=', False),
('share', '=', False),
]
users = self.env['res.users'].search(domain)
max_db_users = int(self.env['ir.config_parameter'].get_param(
MAX_DB_USER_PARAM
))
if max_db_users > 0 and len(users) >= max_db_users:
return True
company = self.env.user.company_id
company_users = users.filtered(lambda r: r.company_id.id == company.id)
if company.max_users > 0 and len(company_users) >= company.max_users:
return True
return False
@api.multi
def copy(self, default=None):
"""
Override method to make sure the Thresholds aren't met before
creating a new user
"""
if self._check_thresholds():
raise ValidationError(_(
'Cannot add user - Maximum number of allowed users reached'
))
return super(ResUsers, self).copy(default=default)
@api.multi
def create(self, vals):
"""
Override method to make sure the Thresholds aren't met before
creating a new user
"""
if self._check_thresholds():
raise ValidationError(_(
'Cannot add user - Maximum number of allowed users reached'
))
return super(ResUsers, self).create(vals)
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
submenu=False):
""" Hide Max User Field when the env var to hide the field is set """
res = super(ResUsers, self).fields_view_get(
view_id, view_type, toolbar, submenu
)
if THRESHOLD_HIDE:
doc = etree.XML(res['arch'])
for node in doc.xpath("//group[@name='user_threshold']"):
node.getparent().remove(node)
res['arch'] = etree.tostring(doc, pretty_print=True)
return res
@api.multi
def write(self, vals):
"""
Override write to verify that membership of the Threshold Manager
group is not able to be set by users outside that group
"""
thold_group = self.env.ref(THRESHOLD_MANAGER, raise_if_not_found=False)
if thold_group:
user_is_manager = self.env.user.has_group(THRESHOLD_MANAGER)
if vals.get('threshold_exempt') and not user_is_manager:
raise AccessError(_(
'You must be a member of the `User Threshold Manager`'
' group to grant threshold exemptions.'
))
is_add_group = vals.get('in_group_%s' % thold_group.id)
if is_add_group and not user_is_manager:
raise AccessError(_(
'You must be a member of the `User Threshold Manager`'
' group to grant access to it.'
))
return super(ResUsers, self).write(vals)

8
user_threshold/tests/__init__.py

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from . import test_ir_config_parameter
from . import test_res_company
from . import test_res_groups
from . import test_res_users

31
user_threshold/tests/common.py

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import random
import string
from odoo.tests.common import TransactionCase
MAX_DB_USER_PARAM = 'user.threshold.database'
class Common(TransactionCase):
def _create_test_user(self):
""" Create a user for testing """
user = self.env.ref('base.user_demo').copy()
rand_name = ''.join([
random.choice(string.ascii_letters) for n in xrange(10)
])
user.write({'login': rand_name})
return user
def _add_user_to_group(self, user):
""" Add a given user Record to the threshold manager group """
th_group = self.env.ref('user_threshold.group_threshold_manager')
system_group = self.env.ref('base.group_system')
user.write({
'in_group_%s' % th_group.id: True,
'in_group_%s' % system_group.id: True
})

87
user_threshold/tests/test_ir_config_parameter.py

@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from odoo.exceptions import AccessError
from .common import Common, MAX_DB_USER_PARAM
class TestIrConfigParameter(Common):
def _get_param(self):
return self.env['ir.config_parameter'].search([
('key', '=', MAX_DB_USER_PARAM),
])
def test_can_set(self):
"""
It should test that users in the Threshold Manager group can
update the parameter
"""
mdl = self.env['ir.config_parameter']
u = self._create_test_user()
self._add_user_to_group(u)
exp = '20'
mdl.sudo(u.id).set_param(MAX_DB_USER_PARAM, exp)
self.assertEquals(mdl.get_param(MAX_DB_USER_PARAM), exp)
def test_cannot_set(self):
"""
It should test that users NOT in the Threshold Manager group
cannot alter the parameter
"""
u = self._create_test_user()
with self.assertRaises(AccessError):
self.env['ir.config_parameter'].sudo(u.id).set_param(
MAX_DB_USER_PARAM, 20
)
def test_can_unlink(self):
"""
It should test that users in the Threshold Manager group can
unlink the Threshold Param
"""
u = self._create_test_user()
self._add_user_to_group(u)
param = self._get_param()
self.assertTrue(param.sudo(u.id).unlink())
def test_cannot_unlink(self):
"""
It should test that users outside the Threshold Manager group
cannot unlink the Threshold Param
"""
u = self._create_test_user()
param = self._get_param()
system_group = self.env.ref('base.group_system')
u.write({'in_group_%s' % system_group.id: True})
with self.assertRaises(AccessError):
param.sudo(u.id).unlink()
def test_can_write(self):
"""
It should test that users in the Threshold Manager group can
write the Threshold Param
"""
u = self._create_test_user()
self._add_user_to_group(u)
param = self._get_param()
res = '10'
param.sudo(u.id).write({'value': res})
self.assertEquals(param.value, res)
def test_cannot_write(self):
"""
It should test that users outside the Threshold Manager group
cannot write the Threshold Param
"""
u = self._create_test_user()
system_group = self.env.ref('base.group_system')
access_group = self.env.ref('base.group_erp_manager')
u.write({
'in_group_%s' % system_group.id: True,
'in_group_%s' % access_group.id: True,
})
param = self._get_param()
with self.assertRaises(AccessError):
param.sudo(u.id).write({'value': '10'})

44
user_threshold/tests/test_res_company.py

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from lxml import etree
from odoo.exceptions import AccessError
from .common import Common
class TestResCompany(Common):
def test_fields_view_get(self):
"""
It should verify that setting THRESHOLD_HIDE removes the parameter
from the view
"""
import odoo.addons.user_threshold.models.res_company as mdl
mdl.THRESHOLD_HIDE = True
view = self.env.ref('user_threshold.view_company_form')
c = self.env['res.company'].browse(1)
ret = c.fields_view_get(view.id)
doc = etree.XML(ret['arch'])
self.assertEquals(doc.xpath("//field[@name='max_users']"), [])
def test_can_write_max_users(self):
"""
It should restrict the max users parameter to Threshold Managers
"""
u = self._create_test_user()
self._add_user_to_group(u)
c = self.env['res.company'].browse(1)
res = 10
c.sudo(u.id).write({'max_users': res})
self.assertEquals(c.max_users, res)
def test_cannot_write_max_users(self):
"""
It should restrict the max users parameter to Threshold Managers
"""
u = self._create_test_user()
c = self.env['res.company'].browse(1)
with self.assertRaises(AccessError):
c.sudo(u.id).write({'max_users': 10})

34
user_threshold/tests/test_res_groups.py

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from odoo.exceptions import AccessError
from .common import Common
class TestResGroups(Common):
def test_can_write_max_users(self):
"""
It should restrict membership additions to Threshold Managers to
pre-existing members of that group
"""
u = self._create_test_user()
u.write({
'in_group_%s' % self.env.ref('base.group_erp_manager').id: True
})
g = self.env.ref('user_threshold.group_threshold_manager')
with self.assertRaises(AccessError):
g.sudo(u.id).write({'users': self.env.ref('base.user_demo').id})
def test_cannot_write_max_users(self):
"""
It should restrict membership additions to Threshold Managers to
pre-existing members of that group
"""
u = self._create_test_user()
self._add_user_to_group(u)
g = self.env.ref('user_threshold.group_threshold_manager')
demo_user = self.env.ref('base.user_demo')
g.sudo(u.id).write({'users': [(4, [demo_user.id])]})
self.assertTrue(demo_user.id in g.users.ids)

127
user_threshold/tests/test_res_users.py

@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from lxml import etree
from odoo.exceptions import AccessError, ValidationError
from .common import Common, MAX_DB_USER_PARAM
class TestResUsers(Common):
def setUp(self):
super(TestResUsers, self).setUp()
self.env['ir.config_parameter'].set_param(MAX_DB_USER_PARAM, '0')
def test_copy_global(self):
"""
It should restrict the user count in copy() as prescribed by the
global threshold parameter
"""
self.env['ir.config_parameter'].set_param(MAX_DB_USER_PARAM, 3)
self._create_test_user()
with self.assertRaises(ValidationError):
self._create_test_user()
def test_create_global(self):
"""
It should restrict the user count as prescribed by the global
threshold parameter
"""
self.env['ir.config_parameter'].set_param(MAX_DB_USER_PARAM, 3)
self._create_test_user()
with self.assertRaises(ValidationError):
self.env['res.users'].create({
'login': 'Derp Derpington',
'email': 'dderpington@example.com',
'notify_email': 'always',
})
def test_copy_company(self):
"""
It should restrict the user count in copy() as prescribed by the
companies threshold parameter
"""
c = self.env['res.company'].browse(1)
c.max_users = 3
self._create_test_user()
with self.assertRaises(ValidationError):
self._create_test_user()
def test_create_company(self):
"""
It should restrict the user count as prescribed by the companies
threshold parameter
"""
c = self.env['res.company'].browse(1)
c.max_users = 3
self._create_test_user()
with self.assertRaises(ValidationError):
self.env['res.users'].create({
'login': 'Derp Derpington',
'email': 'dderpington@example.com',
'notify_email': 'always',
})
def test_fields_view_get(self):
"""
It should verify that setting THRESHOLD_HIDE removes the parameter
from the view
"""
import odoo.addons.user_threshold.models.res_users as mdl
mdl.THRESHOLD_HIDE = True
view = self.env.ref('user_threshold.view_users_form')
u = self._create_test_user()
ret = u.fields_view_get(view.id)
doc = etree.XML(ret['arch'])
self.assertEquals(doc.xpath("//group[@name='user_threshold']"), [])
def test_cannot_write_exempt(self):
"""
It should restrict the threshold exempt parameter to Threshold
Managers
"""
u = self._create_test_user()
tu = self._create_test_user()
with self.assertRaises(AccessError):
tu.sudo(u.id).write({'threshold_exempt': True})
def test_can_write_exempt(self):
"""
It should restrict the threshold exempt parameter to Threshold
Managers
"""
u = self._create_test_user()
self._add_user_to_group(u)
tu = self._create_test_user()
tu.sudo(u.id).write({'threshold_exempt': True})
self.assertEquals(tu.threshold_exempt, True)
def test_cannot_write_group(self):
"""
It should restrict additions to the Threshold Managers to users in
that group
"""
u = self._create_test_user()
u.write({
'in_group_%s' % self.env.ref('base.group_erp_manager').id: True
})
tu = self._create_test_user()
th_group = self.env.ref('user_threshold.group_threshold_manager')
with self.assertRaises(AccessError):
tu.sudo(u.id).write({'in_group_%s' % th_group.id: True})
def test_can_write_group(self):
"""
It should restrict additions to the Threshold Managers to users in
that group
"""
u = self._create_test_user()
self._add_user_to_group(u)
tu = self._create_test_user()
th_group = self.env.ref('user_threshold.group_threshold_manager')
tu.sudo(u.id).write({'in_group_%s' % th_group.id: True})
self.assertEquals(
tu.has_group('user_threshold.group_threshold_manager'), True
)

19
user_threshold/views/res_company_view.xml

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2017 LasLabs Inc.
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
-->
<odoo>
<record id="view_company_form" model="ir.ui.view">
<field name="name">res.company.form</field>
<field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form" />
<field name="arch" type="xml">
<xpath expr="//notebook/page[1]/group/group[1]" position="inside">
<field string="Maximum Users" name="max_users" />
</xpath>
</field>
</record>
</odoo>

22
user_threshold/views/res_users_view.xml

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2017 LasLabs Inc.
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
-->
<odoo>
<record id="view_users_form" model="ir.ui.view">
<field name="name">res.users.form</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form" />
<field name="arch" type="xml">
<xpath expr="//page[@name='access_rights']" position="inside">
<group name="user_threshold" string="User Threshold">
<field name="threshold_exempt"
string="Exempt From Threshold" />
</group>
</xpath>
</field>
</record>
</odoo>
Loading…
Cancel
Save