Skip to content

Commit 3914dc5

Browse files
committed
[ADD] util.invert_boolean_field
New helper for renaming and inverting the meaning of a boolean field. closes #127 Signed-off-by: Christophe Simonis (chs) <[email protected]>
1 parent 6aa4106 commit 3914dc5

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

src/base/tests/test_util.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,63 @@ def test_create_cron(self):
807807
self.assertEqual(cron.code, "answer = 42")
808808

809809

810+
class TestField(UnitTestCase):
811+
def test_invert_boolean_field(self):
812+
cr = self.env.cr
813+
814+
with self.assertRaises(ValueError):
815+
util.invert_boolean_field(cr, "res.partner", "name", "nom")
816+
817+
fltr = self.env["ir.filters"].create(
818+
{"name": "test", "model_id": "ir.model.data", "domain": "[('noupdate', '=', True)]"}
819+
)
820+
821+
query = """
822+
SELECT {0}, count(*)
823+
FROM ir_model_data
824+
GROUP BY {0}
825+
"""
826+
827+
cr.execute(util.format_query(cr, query, "noupdate"))
828+
initial_repartition = dict(cr.fetchall())
829+
830+
# util.parallel_execute will `commit` the cursor and create new ones
831+
# as we are in a test, we should not commit as we are in a subtransaction
832+
with mock.patch.object(cr, "commit", lambda: ...):
833+
util.invert_boolean_field(cr, "ir.model.data", "noupdate", "yesupdate")
834+
835+
util.invalidate(fltr)
836+
self.assertEqual(literal_eval(fltr.domain), ["!", ("yesupdate", "=", True)])
837+
838+
cr.execute(util.format_query(cr, query, "yesupdate"))
839+
inverted_repartition = dict(cr.fetchall())
840+
841+
self.assertEqual(inverted_repartition[False], initial_repartition[True])
842+
self.assertEqual(inverted_repartition[True], initial_repartition[False] + initial_repartition.get(None, 0))
843+
self.assertEqual(inverted_repartition.get(None, 0), 0)
844+
845+
# rename back
846+
with mock.patch.object(cr, "commit", lambda: ...):
847+
util.rename_field(cr, "ir.model.data", "yesupdate", "noupdate")
848+
849+
util.invalidate(fltr)
850+
self.assertEqual(literal_eval(fltr.domain), ["!", ("noupdate", "=", True)])
851+
852+
# invert with same name; will invert domains and data
853+
with mock.patch.object(cr, "commit", lambda: ...):
854+
util.invert_boolean_field(cr, "ir.model.data", "noupdate", "noupdate")
855+
856+
util.invalidate(fltr)
857+
self.assertEqual(literal_eval(fltr.domain), ["!", "!", ("noupdate", "=", True)])
858+
859+
cr.execute(util.format_query(cr, query, "noupdate"))
860+
back_repartition = dict(cr.fetchall())
861+
862+
# merge None into False in the initial repartition
863+
initial_repartition[False] += initial_repartition.pop(None, 0)
864+
self.assertEqual(back_repartition, initial_repartition)
865+
866+
810867
class TestHelpers(UnitTestCase):
811868
def test_model_table_convertion(self):
812869
cr = self.env.cr

src/util/fields.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def make_index_name(table_name, column_name):
4848
alter_column_type,
4949
column_exists,
5050
column_type,
51+
explode_execute,
5152
explode_query_range,
5253
format_query,
5354
get_value_or_en_translation,
@@ -580,6 +581,26 @@ def rename_field(cr, model, old, new, update_references=True, domain_adapter=Non
580581
rename_field(cr, inh.model, old, new, update_references=update_references, skip_inherit=skip_inherit)
581582

582583

584+
def invert_boolean_field(cr, model, old, new, skip_inherit=()):
585+
"""Rename a boolean field and invert its value."""
586+
_validate_model(model)
587+
table = table_of_model(cr, model)
588+
if column_type(cr, table, old) != "bool":
589+
raise ValueError("Field {!r} of model {!r} is not a stored boolean field".format(old, model))
590+
591+
def _adapter(leaf, _or, _neg):
592+
return ["!", leaf]
593+
594+
if old == new:
595+
# same name but inverted value, just adapt domains
596+
adapt_domains(cr, model, old, new, adapter=_adapter, skip_inherit=skip_inherit, force_adapt=True)
597+
else:
598+
rename_field(cr, model, old, new, update_references=True, domain_adapter=_adapter, skip_inherit=skip_inherit)
599+
600+
query = format_query(cr, "UPDATE {t} SET {c} = {c} IS NOT TRUE", t=table, c=new)
601+
explode_execute(cr, query, table=table)
602+
603+
583604
def convert_field_to_html(cr, model, field, skip_inherit=()):
584605
_validate_model(model)
585606
table = table_of_model(cr, model)

0 commit comments

Comments
 (0)