Skip to content

Commit

Permalink
🌈 refactoring to convert existing field to website-dependent one more…
Browse files Browse the repository at this point in the history
… easily
  • Loading branch information
Ivan Yelizariev committed Aug 23, 2018
1 parent ec572dd commit 583312e
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 148 deletions.
2 changes: 1 addition & 1 deletion ir_config_parameter_multi_company/__manifest__.py
Expand Up @@ -4,7 +4,7 @@
"category": "Extra Tools",
# "live_test_url": "",
"images": [],
"version": "11.0.3.0.0",
"version": "11.0.4.0.0",
"application": False,

"author": "IT-Projects LLC, Ivan Yelizariev",
Expand Down
5 changes: 4 additions & 1 deletion ir_config_parameter_multi_company/doc/changelog.rst
@@ -1,6 +1,9 @@
`4.0.0`
-------

- **Fix:** Random errors with multi-website parameters due to wrong cache usage
- **Fix:** random errors with multi-website parameters due to wrong cache usage
- **Improvement:** better naming in Properties
- **Improvement:** refactoring in code

`3.0.0`
-------
Expand Down
132 changes: 11 additions & 121 deletions ir_config_parameter_multi_company/models/ir_config_parameter.py
@@ -1,49 +1,30 @@
import logging

from odoo import models, fields, api, _, tools
from odoo import models, fields, api, tools
from odoo.addons.base.ir.ir_config_parameter import IrConfigParameter as IrConfigParameterOriginal, _default_parameters

_logger = logging.getLogger(__name__)
PROP_NAME = _('Default value for "%s"')

DATABASE_SECRET_KEY = 'database.secret'

# params that has to be shared across all companies
SHARED_KEYS = [
'database.expiration_date',
]
FIELD_NAME = 'value'


class IrConfigParameter(models.Model):
_inherit = 'ir.config_parameter'
_inherit = ['ir.config_parameter', 'website_dependent.mixin']
_name = 'ir.config_parameter'

value = fields.Text(company_dependent=True, website_dependent=True)

@api.multi
def _get_property(self, for_default=False):
self.ensure_one()

domain = self.env['ir.property']._get_domain('value', self._name)
domain += [('res_id', '=', '%s,%s' % (self._name, self.id))]
if for_default:
# find: ('company_id', 'in', [company_id, False])
# update to: ('company_id', 'in', [False])
domain = [
('company_id', 'in', [False])
if x[0] == 'company_id'
else
x
for x in domain
]

prop = self.env['ir.property'].search(domain)
return prop

@api.model
def create(self, vals):
res = super(IrConfigParameter, self).create(vals)
# make value company independent
res._force_default(vals.get('value'))
res._force_default(FIELD_NAME, vals.get('value'))
return res

@api.multi
Expand All @@ -53,60 +34,13 @@ def write(self, vals):
if value:
for r in self:
if r.key in SHARED_KEYS:
r._force_default(value)
r._force_default(FIELD_NAME, value)

if 'key' in vals:
# Change property name after renaming the parameter to avoid confusions
self.ensure_one() # it's not possible to update key on multiple records
name = PROP_NAME % vals.get('key')
prop = self._get_property(for_default=True)
prop.name = name
if any(k in vals for k in ('key', 'value')):
self._update_properties_label(FIELD_NAME)

return res

def _force_default(self, value):
"""Remove company-dependent values and keeps only default one"""
self.ensure_one()
Prop = self.env['ir.property']
domain = Prop._get_domain('value', self._name)

# find all props
props = Prop.search(domain + [
('res_id', '=', '%s,%s' % (self._name, self.id)),
])

default_prop = None
if len(props) == 0:
default_prop = self._create_default_value(value)
elif len(props) == 1:
default_prop = props
else:
default_prop = props.filtered(lambda r: not r.company_id)[:1]
if not default_prop:
default_prop = props[0]

# remove rest properties
(props - default_prop).unlink()

vals = {
'name': PROP_NAME % self.key
}
if default_prop.company_id:
vals['company_id'] = None

if default_prop.get_by_record() != value:
vals['value'] = value

default_prop.write(vals)
self._update_db_value(value)
return self

def _update_db_value(self, value):
"""Store value in db column. We can use it only directly,
because ORM treat value as computed multi-company field"""
self.ensure_one()
self.env.cr.execute("UPDATE ir_config_parameter SET value=%s WHERE id = %s", (value, self.id, ))

@api.model
def reset_database_secret(self):
value = _default_parameters[DATABASE_SECRET_KEY]()
Expand All @@ -132,7 +66,8 @@ def get_param(self, key, default=False):
# If we have empty database.secret, we reset it automatically
# otherwise admin cannot even login

# TODO: we don't really need to reset database.secret, because in current version of the module column value is presented and up-to-date. Keep it until we are sure, that without this redefinition everything works after migration from previous versions fo the module.
# TODO: remove this block in odoo 12
# we don't really need to reset database.secret, because in current version of the module column value is presented and up-to-date. Keep it until we are sure, that without this redefinition everything works after migration from previous versions fo the module.

return self_company.reset_database_secret()

Expand All @@ -145,51 +80,6 @@ def _get_param(self, key):
# call undecorated super method. See odoo/tools/cache.py::ormcache and http://decorator.readthedocs.io/en/stable/tests.documentation.html#getting-the-source-code
return IrConfigParameterOriginal._get_param.__wrapped__(self, key)

@api.multi
def _create_default_value(self, value):
"""Set company-independent default value"""
self.ensure_one()
domain = [
('company_id', '=', False),
('res_id', '=', '%s,%s' % (self._name, self.id))
]

existing = self.env['ir.property'].search(domain)
if existing:
# already exists
return existing

_logger.debug('Create default value for %s', self.key)
return self.env['ir.property'].create({
'fields_id': self.env.ref('base.field_ir_config_parameter_value').id,
'res_id': '%s,%s' % (self._name, self.id),
'name': PROP_NAME % self.key,
'value': value,
'type': 'text',
})

def _auto_init(self):
cr = self.env.cr
# rename "value" to "value_tmp"
# to don't lose values, because during installation the column "value" is deleted
cr.execute("ALTER TABLE ir_config_parameter RENAME COLUMN value TO value_tmp")

def post_init_callback():
self._post_init()
self.pool.post_init(post_init_callback)
self._auto_init_website_dependent(FIELD_NAME)
return super(IrConfigParameter, self)._auto_init()

def _post_init(self):
cr = self.env.cr

# rename "value_tmp" back to "value_tmp"
cr.execute("ALTER TABLE ir_config_parameter RENAME COLUMN value_tmp TO value")

for r in self.env['ir.config_parameter'].sudo().search([]):
cr.execute("SELECT key,value FROM ir_config_parameter WHERE id = %s", (r.id, ))
res = cr.dictfetchone()
value = res.get('value')
# value may be empty after migration from previous module version
if value:
# create default value if it doesn't exist
r._create_default_value(value)
24 changes: 2 additions & 22 deletions ir_config_parameter_multi_company/models/ir_property.py
Expand Up @@ -7,26 +7,6 @@ class IrProperty(models.Model):
@api.multi
def write(self, vals):
res = super(IrProperty, self).write(vals)
self._update_config_parameter_value()
return res

@api.multi
def _update_config_parameter_value(self):
"""Check for default value in ir.config_parameter
and copy value to "value" column"""
field = self.env.ref('base.field_ir_config_parameter_value')
for r in self:
if r.fields_id != field:
# It's not for ir.config_parameter
continue
if r.company_id:
# it's not default value
continue
if not r.res_id:
# Paramater is not specified
continue
# Default value is updated. Set new value in column "value"
model, res_id = r.res_id.split(',')
value = r.get_by_record()
param = self.env['ir.config_parameter'].browse(int(res_id))
param._update_db_value(value)
self._update_db_value_website_dependent(field)
return res
26 changes: 24 additions & 2 deletions web_website/README.rst
Expand Up @@ -8,8 +8,30 @@

Technical module to switch Websites in Backend similarly to Company Switcher. On changing it update field **backend_website_id** in ``res.users``.

Also, introduces new field attribute ``website_dependent``, which can be used
only along with ``company_dependent``. See `<models/test_website.py>`_ and `<tests/test_website_dependent.py>`_ as an example of usage.
website_dependent
=================

The module adds new field attribute ``website_dependent``, which is analog of ``company_dependent``, but for websites.

See `<models/test_website.py>`_ and `<tests/test_website_dependent.py>`_ to understand how it works.

If you need to convert existing field to a website-dependent field it's not
enough just to add the attributes. You need additional stuff to make your module
safely installable and uninstallable. See module
``ir_config_parameter_multi_company`` as an example. Things to do:

* extend ``ir.property``'s ``write`` to call ``_update_db_value_website_dependent``
* Add to the field both ``company_dependent=True`` and ``website_dependent=True``
* In the field's module extend following methods:

* ``create`` -- call ``_force_default``
* ``write`` -- call ``_update_properties_label``
* ``_auto_init`` -- call ``_auto_init_website_dependent``

* In the field's module add ``uninstall_hook``:

* remove field's properties
* update module where the field is from

Roadmap
=======
Expand Down
2 changes: 1 addition & 1 deletion web_website/__manifest__.py
Expand Up @@ -6,7 +6,7 @@
"category": "Hidden",
# "live_test_url": "",
"images": [],
"version": "11.0.1.0.1",
"version": "11.0.2.0.0",
"application": False,

"author": "IT-Projects LLC, Ivan Yelizariev",
Expand Down
4 changes: 4 additions & 0 deletions web_website/doc/changelog.rst
@@ -1,3 +1,7 @@
`2.0.0`
-------
- **Improvement:** Code is refactored to convert existing field to website-dependent one in a more easy way

`1.0.1`
-------
- **Fix:** Website Switcher didn't work for non-admin users
Expand Down
1 change: 1 addition & 0 deletions web_website/models/__init__.py
Expand Up @@ -4,3 +4,4 @@
from . import res_config
from . import res_users
from . import ir_http
from . import website_dependent_mixin
22 changes: 22 additions & 0 deletions web_website/models/ir_property.py
Expand Up @@ -107,3 +107,25 @@ def set_multi(self, name, model, values, default_value=None):
_search_domain_website_dependent=True,
create_website_dependent=True,
)).set_multi(name, model, values, default_value=default_value)

@api.multi
def _update_db_value_website_dependent(self, field):
"""Update db value if it's a default value"""
for r in self:
if r.fields_id != field:
# It's another field
continue
if r.company_id:
# it's not default value
continue
# r.website_id is empty here,
# because otherwise r.company_id is not empty too
if not r.res_id:
# It's not record-specific
continue
# Default value is updated. Set new value in db column
model, res_id = r.res_id.split(',')
value = r.get_by_record()
model = field.model_id.model
record = self.env[model].browse(int(res_id))
record._update_db_value(field.name, value)

0 comments on commit 583312e

Please sign in to comment.