Skip to content

Commit 910d30c

Browse files
committed
[IMP] util.remove_field
Add option to keep binary data as attachment. closes #258 Signed-off-by: Christophe Simonis (chs) <[email protected]>
1 parent dee26f0 commit 910d30c

File tree

1 file changed

+61
-22
lines changed

1 file changed

+61
-22
lines changed

src/util/fields.py

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def make_index_name(table_name, column_name):
5050
from .misc import SelfPrintEvalContext, log_progress, version_gte
5151
from .orm import env, invalidate
5252
from .pg import (
53+
SQLStr,
5354
alter_column_type,
5455
column_exists,
5556
column_type,
@@ -182,7 +183,7 @@ def filter_value(key, value):
182183
return changed
183184

184185

185-
def remove_field(cr, model, fieldname, cascade=False, drop_column=True, skip_inherit=()):
186+
def remove_field(cr, model, fieldname, cascade=False, drop_column=True, skip_inherit=(), keep_as_attachments=False):
186187
"""
187188
Remove a field and its references from the database.
188189
@@ -196,6 +197,8 @@ def remove_field(cr, model, fieldname, cascade=False, drop_column=True, skip_inh
196197
:param bool drop_column: whether the field's column is dropped
197198
:param list(str) or str skip_inherit: list of inheriting models to skip the removal
198199
of the field, use `"*"` to skip all
200+
:param bool keep_as_attachments: for binary fields, whether the data should be kept
201+
as attachments
199202
"""
200203
_validate_model(model)
201204

@@ -353,8 +356,20 @@ def adapter(leaf, is_or, negated):
353356
defaults.pop(fieldname, None)
354357
cr.execute("UPDATE mail_alias SET alias_defaults = %s WHERE id = %s", [repr(defaults), alias_id])
355358

356-
# if field was a binary field stored as attachment, clean them...
357-
if column_exists(cr, "ir_attachment", "res_field"):
359+
table = table_of_model(cr, model)
360+
# NOTE table_exists is needed to avoid altering views
361+
stored = table_exists(cr, table) and column_exists(cr, table, fieldname)
362+
363+
if keep_as_attachments:
364+
if stored and column_type(cr, table, fieldname) == "bytea":
365+
_extract_data_as_attachment(cr, model, fieldname, set_res_field=False)
366+
if column_exists(cr, "ir_attachment", "res_field"):
367+
query = cr.mogrify(
368+
"UPDATE ir_attachment SET res_field = NULL WHERE res_model = %s AND res_field = %s", [model, fieldname]
369+
).decode()
370+
explode_execute(cr, query, table="ir_attachment")
371+
372+
elif column_exists(cr, "ir_attachment", "res_field"):
358373
parallel_execute(
359374
cr,
360375
explode_query_range(
@@ -367,13 +382,20 @@ def adapter(leaf, is_or, negated):
367382
)
368383

369384
table = table_of_model(cr, model)
370-
# NOTE table_exists is needed to avoid altering views
371-
if drop_column and table_exists(cr, table) and column_exists(cr, table, fieldname):
385+
if drop_column and stored:
372386
remove_column(cr, table, fieldname, cascade=cascade)
373387

374388
# remove field on inherits
375389
for inh in for_each_inherit(cr, model, skip_inherit):
376-
remove_field(cr, inh.model, fieldname, cascade=cascade, drop_column=drop_column, skip_inherit=skip_inherit)
390+
remove_field(
391+
cr,
392+
inh.model,
393+
fieldname,
394+
cascade=cascade,
395+
drop_column=drop_column,
396+
skip_inherit=skip_inherit,
397+
keep_as_attachments=keep_as_attachments,
398+
)
377399

378400

379401
def make_field_non_stored(cr, model, field, selectable=False):
@@ -884,18 +906,45 @@ def _convert_field_to_property(
884906

885907

886908
def convert_binary_field_to_attachment(cr, model, field, encoded=True, name_field=None):
909+
_extract_data_as_attachment(cr, model, field, encoded, name_field)
910+
# free PG space
911+
table = table_of_model(cr, model)
912+
remove_column(cr, table, field)
913+
914+
915+
def _extract_data_as_attachment(cr, model, field, encoded=True, name_field=None, set_res_field=True):
887916
_validate_model(model)
888917
table = table_of_model(cr, model)
889918
if not column_exists(cr, table, field):
890919
return
891-
name_query = "COALESCE({0}, '{1}('|| id || ').{2}')".format(
892-
name_field if name_field else "NULL",
893-
model.title().replace(".", ""),
894-
field,
920+
name_query = cr.mogrify(
921+
format_query(cr, "COALESCE({}, CONCAT(%s, '(', id, ').', %s)", name_field if name_field else SQLStr("NULL")),
922+
[model.title().replace(".", ""), field],
923+
).decode()
924+
925+
if not column_exists(cr, "ir_attachment", "res_field"):
926+
res_field_query = SQLStr("")
927+
elif set_res_field:
928+
res_field_query = SQLStr(cr.mogrify(", res_field = %s", [field]).decode())
929+
else:
930+
res_field_query = SQLStr(", res_field = NULL")
931+
932+
update_query = format_query(
933+
cr,
934+
"""
935+
UPDATE ir_attachment
936+
SET res_model = %s,
937+
res_id = %s
938+
{}
939+
WHERE id = %s
940+
""",
941+
res_field_query,
895942
)
896943

897944
cr.execute(format_query(cr, "SELECT count(*) FROM {} WHERE {} IS NOT NULL", table, field))
898945
[count] = cr.fetchone()
946+
if count == 0:
947+
return
899948

900949
A = env(cr)["ir.attachment"]
901950
iter_cur = cr._cnx.cursor("fetch_binary")
@@ -920,21 +969,11 @@ def convert_binary_field_to_attachment(cr, model, field, encoded=True, name_fiel
920969
if not encoded:
921970
data = base64.b64encode(data) # noqa: PLW2901
922971
att = A.create({"name": name, "datas": data, "type": "binary"})
923-
cr.execute(
924-
"""
925-
UPDATE ir_attachment
926-
SET res_model = %s,
927-
res_id = %s,
928-
res_field = %s
929-
WHERE id = %s
930-
""",
931-
[model, rid, field, att.id],
932-
)
972+
973+
cr.execute(update_query, [model, rid, att.id])
933974
invalidate(att)
934975

935976
iter_cur.close()
936-
# free PG space
937-
remove_column(cr, table, field)
938977

939978

940979
if version_gte("16.0"):

0 commit comments

Comments
 (0)