From 8b00fc1544fb579c75359dcf8088dd5ea5d2f030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sun, 7 Mar 2021 19:07:53 +0100 Subject: [PATCH] Improve report and CSV for tree structure issue10116 review341431002 --- CHANGELOG | 3 ++ tryton/gui/window/form.py | 37 ++++++++++++--- tryton/gui/window/view_form/screen/screen.py | 19 ++++++++ tryton/gui/window/view_form/view/list.py | 44 ++++++++++++++++++ tryton/gui/window/win_export.py | 47 ++++++++++++++++---- 5 files changed, 134 insertions(+), 16 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0af5e8e93c..c897bfae84 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +* Add paths and model context to action data +* Add indentation in CSV export of tree +* Manage which records to use for actions * Handle device cookie * Add breadcrumb as title of window form * Display revision on dialog diff --git a/tryton/gui/window/form.py b/tryton/gui/window/form.py index 895a9d787f..d181dae7c2 100644 --- a/tryton/gui/window/form.py +++ b/tryton/gui/window/form.py @@ -7,6 +7,7 @@ import gettext import locale import os import tempfile +from itertools import zip_longest from gi.repository import Gdk, GLib, Gtk @@ -339,7 +340,14 @@ class Form(SignalEvent, TabContent): def do_export(self, widget, export): if not self.modified_save(): return - ids = [r.id for r in self.screen.selected_records] + if (self.screen.current_view + and self.screen.current_view.view_type == 'tree' + and self.screen.current_view.children_field): + ids = [r.id for r in self.screen.listed_records] + paths = self.screen.listed_paths + else: + ids = [r.id for r in self.screen.selected_records] + paths = self.screen.selected_paths fields = [f['name'] for f in export['export_fields.']] data = RPCExecute( 'model', self.model, 'export_data', ids, fields, @@ -352,8 +360,10 @@ class Form(SignalEvent, TabContent): with open(fname, 'w') as fp: writer = csv.writer(fp, delimiter=delimiter) writer.writerow(fields) - for row in data: - writer.writerow(WinExport.format_row(row)) + for row, path in zip_longest(data, paths or []): + indent = len(path) - 1 if path else 0 + if row: + writer.writerow(WinExport.format_row(row, indent=indent)) os.close(fileno) common.file_open(fname, 'csv') @@ -575,11 +585,20 @@ class Form(SignalEvent, TabContent): action = action.copy() record_id = (self.screen.current_record.id if self.screen.current_record else None) - record_ids = [r.id for r in self.screen.selected_records] + if action.get('records') == 'listed': + record_ids = [r.id for r in self.screen.listed_records] + record_paths = self.screen.listed_paths + else: + record_ids = [r.id for r in self.screen.selected_records] + record_paths = self.screen.selected_paths data = { 'model': self.screen.model_name, + 'model_context': ( + self.screen.context_screen.model_name + if self.screen.context_screen else None), 'id': record_id, 'ids': record_ids, + 'paths': record_paths, } Action.execute(action, data, context=self.screen.local_context) @@ -741,11 +760,15 @@ class Form(SignalEvent, TabContent): menuitem = Gtk.MenuItem(label=set_underline(name)) menuitem.set_use_underline(True) menuitem.connect('activate', lambda m, func: func({ - 'model': self.model, - 'ids': [r.id - for r in self.screen.selected_records], + 'model': self.screen.model_name, + 'model_context': ( + self.screen.context_screen.model_name + if self.screen.context_screen else None), 'id': (self.screen.current_record.id if self.screen.current_record else None), + 'ids': [r.id + for r in self.screen.selected_records], + 'paths': self.screen.selected_paths, }), func) menuitem._update_action = True menu.add(menuitem) diff --git a/tryton/gui/window/view_form/screen/screen.py b/tryton/gui/window/view_form/screen/screen.py index 77774ac7c0..9b5e97d25b 100644 --- a/tryton/gui/window/view_form/screen/screen.py +++ b/tryton/gui/window/view_form/screen/screen.py @@ -1124,6 +1124,25 @@ class Screen(SignalEvent): def selected_records(self): return self.current_view.selected_records if self.current_view else [] + @property + def selected_paths(self): + if self.current_view and self.current_view.view_type == 'tree': + return self.current_view.get_selected_paths() + + @property + def listed_records(self): + if self.current_view and self.current_view.view_type == 'tree': + return self.current_view.listed_records + elif self.current_record: + return [self.current_record] + else: + return [] + + @property + def listed_paths(self): + if self.current_view and self.current_view.view_type == 'tree': + return self.current_view.get_listed_paths() + def clear(self): self.current_record = None self.group.clear() diff --git a/tryton/gui/window/view_form/view/list.py b/tryton/gui/window/view_form/view/list.py index e10a2ee276..33422bda2c 100644 --- a/tryton/gui/window/view_form/view/list.py +++ b/tryton/gui/window/view_form/view/list.py @@ -1182,6 +1182,50 @@ class ViewTree(View): id_paths.append(id_path) return id_paths + @property + def listed_records(self): + model = self.treeview.get_model() + if not self.children_field: + return list(model.group) + + def get_listed_records(start): + records = [] + if start: + iter_ = model.get_iter(Gtk.TreePath(start)) + else: + iter_ = None + for idx in range(model.iter_n_children(iter_)): + path = start + (idx,) + iter_ = model.get_iter(path) + record = model.get_value(iter_, 0) + records.append(record) + if self.treeview.row_expanded(Gtk.TreePath(path)): + records += get_listed_records(path) + return records + return get_listed_records(()) + + def get_listed_paths(self): + model = self.treeview.get_model() + if not self.children_field: + return [[r.id] for r in model.group] + + def get_listed_paths(start=None, start_path=None): + paths = [] + if start: + iter_ = model.get_iter(Gtk.TreePath(start)) + else: + iter_ = None + for idx in range(model.iter_n_children(iter_)): + path = start + (idx,) + iter_ = model.get_iter(path) + record = model.get_value(iter_, 0) + id_path = start_path + [record.id] + paths.append(id_path) + if self.treeview.row_expanded(Gtk.TreePath(path)): + paths += get_listed_paths(path, id_path) + return paths + return get_listed_paths((), []) + def select_nodes(self, nodes): selection = self.treeview.get_selection() if not nodes: diff --git a/tryton/gui/window/win_export.py b/tryton/gui/window/win_export.py index ee967911b5..0a8775bfee 100644 --- a/tryton/gui/window/win_export.py +++ b/tryton/gui/window/win_export.py @@ -8,6 +8,7 @@ import gettext import json import locale import urllib.parse +from itertools import zip_longest from numbers import Number from gi.repository import Gdk, GObject, Gtk @@ -42,6 +43,13 @@ class WinExport(WinCSV): def context(self): return self.screen.context + @property + def screen_is_tree(self): + return bool( + self.screen.current_view + and self.screen.current_view.view_type == 'tree' + and self.screen.current_view.children_field) + def add_buttons(self, box): button_save_export = Gtk.Button( label=_('_Save Export'), stock=None, use_underline=True) @@ -116,7 +124,10 @@ class WinExport(WinCSV): self.selected_records, expand=True, fill=True, padding=3) self.selected_records.append_text(_("Listed Records")) self.selected_records.append_text(_("Selected Records")) - self.selected_records.set_active(1) + if not self.screen_is_tree: + self.selected_records.set_active(1) + else: + self.selected_records.set_active(0) self.ignore_search_limit = Gtk.CheckButton( label=_("Ignore search limit")) @@ -125,7 +136,8 @@ class WinExport(WinCSV): self.selected_records.connect( 'changed', - lambda w: self.ignore_search_limit.set_visible(not w.get_active())) + lambda w: self.ignore_search_limit.set_visible( + not w.get_active() and not self.screen_is_tree)) def add_csv_header_param(self, box): self.add_field_names = Gtk.CheckButton(label=_("Add field names")) @@ -320,6 +332,17 @@ class WinExport(WinCSV): if self.selected_records.get_active(): ids = [r.id for r in self.screen.selected_records] + paths = self.screen.selected_paths + try: + data = RPCExecute( + 'model', self.model, 'export_data', + ids, fields, + context=self.context) + except RPCException: + data = [] + elif self.screen_is_tree: + ids = [r.id for r in self.screen.listed_records] + paths = self.screen.listed_paths try: data = RPCExecute( 'model', self.model, 'export_data', @@ -328,6 +351,7 @@ class WinExport(WinCSV): except RPCException: data = [] else: + paths = None domain = self.screen.search_domain( self.screen.screen_container.get_text()) if self.ignore_search_limit.get_active(): @@ -346,16 +370,16 @@ class WinExport(WinCSV): fname = common.file_selection(_('Save As...'), action=Gtk.FileChooserAction.SAVE) if fname: - self.export_csv(fname, fields2, data) + self.export_csv(fname, fields2, data, paths) else: fileno, fname = tempfile.mkstemp( '.csv', common.slugify(self.name) + '_') - self.export_csv(fname, fields2, data, popup=False) + self.export_csv(fname, fields2, data, paths, popup=False) os.close(fileno) common.file_open(fname, 'csv') self.destroy() - def export_csv(self, fname, fields, data, popup=True): + def export_csv(self, fname, fields, data, paths, popup=True): encoding = self.csv_enc.get_active_text() or 'UTF-8' locale_format = self.csv_locale.get_active() @@ -366,8 +390,11 @@ class WinExport(WinCSV): delimiter=self.get_delimiter()) if self.add_field_names.get_active(): writer.writerow(fields) - for row in data: - writer.writerow(self.format_row(row, locale_format)) + for row, path in zip_longest(data, paths or []): + indent = len(path) - 1 if path else 0 + if row: + writer.writerow(self.format_row( + row, indent=indent, locale_format=locale_format)) if popup: if len(data) == 1: common.message(_('%d record saved.') % len(data)) @@ -380,9 +407,9 @@ class WinExport(WinCSV): return False @classmethod - def format_row(cls, line, locale_format=True): + def format_row(cls, line, indent=0, locale_format=True): row = [] - for val in line: + for i, val in enumerate(line): if locale_format: if isinstance(val, Number): val = locale.str(val) @@ -397,6 +424,8 @@ class WinExport(WinCSV): val = val.total_seconds() elif isinstance(val, bool): val = int(val) + if i == 0 and indent and isinstance(val, str): + val = ' ' * indent + val row.append(val) return row