Improve report and CSV for tree structure
issue10116 review341431002
This commit is contained in:
1 parent
3e41aed055
commit
8b00fc1544
5 files changed
+134
-16
No files matched your search
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user