changeset b739c7c6a470 in tryton:default
details: https://hg.tryton.org/tryton?cmd=changeset;node=b739c7c6a470
description:
        Improve report and CSV for tree structure

        issue10116
        review341431002
diffstat:

 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(-)

diffs (302 lines):

diff -r 09dd58c192e7 -r b739c7c6a470 CHANGELOG
--- a/CHANGELOG Sun Mar 07 15:40:40 2021 +0100
+++ b/CHANGELOG Sun Mar 07 19:07:53 2021 +0100
@@ -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 -r 09dd58c192e7 -r b739c7c6a470 tryton/gui/window/form.py
--- a/tryton/gui/window/form.py Sun Mar 07 15:40:40 2021 +0100
+++ b/tryton/gui/window/form.py Sun Mar 07 19:07:53 2021 +0100
@@ -7,6 +7,7 @@
 import locale
 import os
 import tempfile
+from itertools import zip_longest
 
 from gi.repository import Gdk, GLib, Gtk
 
@@ -339,7 +340,14 @@
     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 @@
         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 @@
         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 @@
             menuitem = Gtk.MenuItem(label=set_underline(name))
             menuitem.set_use_underline(True)
             menuitem.connect('activate', lambda m, func: func({
-                        'model': self.model,
+                        '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],
-                        'id': (self.screen.current_record.id
-                            if self.screen.current_record else None),
+                        'paths': self.screen.selected_paths,
                         }), func)
             menuitem._update_action = True
             menu.add(menuitem)
diff -r 09dd58c192e7 -r b739c7c6a470 
tryton/gui/window/view_form/screen/screen.py
--- a/tryton/gui/window/view_form/screen/screen.py      Sun Mar 07 15:40:40 
2021 +0100
+++ b/tryton/gui/window/view_form/screen/screen.py      Sun Mar 07 19:07:53 
2021 +0100
@@ -1124,6 +1124,25 @@
     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 -r 09dd58c192e7 -r b739c7c6a470 tryton/gui/window/view_form/view/list.py
--- a/tryton/gui/window/view_form/view/list.py  Sun Mar 07 15:40:40 2021 +0100
+++ b/tryton/gui/window/view_form/view/list.py  Sun Mar 07 19:07:53 2021 +0100
@@ -1182,6 +1182,50 @@
             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 -r 09dd58c192e7 -r b739c7c6a470 tryton/gui/window/win_export.py
--- a/tryton/gui/window/win_export.py   Sun Mar 07 15:40:40 2021 +0100
+++ b/tryton/gui/window/win_export.py   Sun Mar 07 19:07:53 2021 +0100
@@ -8,6 +8,7 @@
 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 @@
     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 @@
             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 @@
 
         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 @@
 
             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 @@
                 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 @@
                 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 @@
                 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 @@
             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 @@
                 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
 

Reply via email to