Add option to show revisions

issue3678
review3501003
This commit is contained in:
Cédric Krier committed 2014-03-18 19:55:29 +01:00
1 parent 61b815d787
commit 086b4ae1ae
12 files changed
+177 -37

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
* Add option to show revisions
* Add a multi selection widget for many2many
* Remove auto-refresh
* Add support of domain for non-relation field
+1
View File
@@ -113,6 +113,7 @@ class Action(object):
action_ctx = decoder.decode(action.get('pyson_context') or '{}')
ctx.update(action_ctx)
ctx.update(context)
action_ctx.update(context)
ctx['context'] = ctx
decoder = PYSONDecoder(ctx)
+17
View File
@@ -168,6 +168,23 @@ class ModelAccess(object):
MODELACCESS = ModelAccess()
class ModelHistory(object):
_models = set()
def load_history(self):
self._models.clear()
try:
self._models.update(rpc.execute('model', 'ir.model',
'list_history', rpc.CONTEXT))
except TrytonServerError:
pass
def __contains__(self, model):
return model in self._models
MODELHISTORY = ModelHistory()
class ViewSearch(object):
searches = None
+5 -1
View File
@@ -154,7 +154,11 @@ class DateEntry(gtk.Entry):
def set_text(self, text):
self._interactive_input = False
try:
# Force position as _on_insert_text use it
position = self.get_position()
self.set_position(0)
gtk.Entry.set_text(self, text)
self.idle_set_position(position)
finally:
self._interactive_input = True
@@ -217,7 +221,7 @@ class DateEntry(gtk.Entry):
res = None
date = self.compute_date(self.get_text())
try:
res = datetime.datetime(*time.strptime(date, self.format)[:6])
res = datetime.datetime.strptime(date, self.format)
except ValueError:
return None
if set_text:
+1
View File
@@ -855,6 +855,7 @@ class Main(object):
for target in (
common.ICONFACTORY.load_icons,
common.MODELACCESS.load_models,
common.MODELHISTORY.load_history,
common.VIEW_SEARCH.load_searches,
):
t = threading.Thread(target=target)
+4 -3
View File
@@ -15,11 +15,12 @@ class Attachment(WinForm):
def __init__(self, record, callback=None):
self.resource = '%s,%s' % (record.model_name, record.id)
self.attachment_callback = callback
context = record.context_get()
context['resource'] = self.resource
screen = Screen('ir.attachment', domain=[
('resource', '=', self.resource),
], mode=['tree', 'form'], context={
'resource': self.resource,
}, exclude_field='resource')
], mode=['tree', 'form'], context=context,
exclude_field='resource')
screen.search_filter()
screen.parent = record
super(Attachment, self).__init__(screen, self.callback,
+58 -18
View File
@@ -11,11 +11,13 @@ from tryton.gui.window import Window
from tryton.gui.window.win_export import WinExport
from tryton.gui.window.win_import import WinImport
from tryton.gui.window.attachment import Attachment
from tryton.gui.window.revision import Revision
from tryton.signal_event import SignalEvent
from tryton.common import message, sur, sur_3b, COLOR_SCHEMES, timezoned_date
import tryton.common as common
from tryton.translate import date_format
from tryton.common import RPCExecute, RPCException
from tryton.common.datetime_strftime import datetime_strftime
from tryton import plugins
from tabcontent import TabContent
@@ -61,6 +63,7 @@ class Form(SignalEvent, TabContent):
(_('_Next'), 'tryton-go-next', 'sig_next', '<tryton>/Form/Next'),
(_('_Search'), 'tryton-find', 'sig_search', '<tryton>/Form/Search'),
(_('View _Logs...'), None, 'sig_logs', None),
(_('Show revisions...'), 'tryton-clock', 'revision', None),
(None,) * 4,
(_('_Close Tab'), 'tryton-close', 'sig_win_close',
'<tryton>/Form/Close'),
@@ -96,8 +99,6 @@ class Form(SignalEvent, TabContent):
domain = []
if view_ids is None:
view_ids = []
if context is None:
context = {}
self.model = model
self.res_id = res_id
@@ -107,7 +108,7 @@ class Form(SignalEvent, TabContent):
self.view_ids = view_ids
self.dialogs = []
self.screen = Screen(self.model, mode=mode, context=self.context,
self.screen = Screen(self.model, mode=mode, context=context,
view_ids=view_ids, domain=domain, limit=limit, order=order,
search_value=search_value, tab_domain=tab_domain)
self.screen.widget.show()
@@ -117,6 +118,12 @@ class Form(SignalEvent, TabContent):
else:
self.name = name
if self.model not in common.MODELHISTORY:
self.menu_def = self.menu_def[:]
# Remove callback to revision
self.menu_def[11] = (self.menu_def[11][:2] + (None,)
+ self.menu_def[11][3:])
self.create_tabcontent()
self.url_entry = url_entry = gtk.Entry()
@@ -154,10 +161,12 @@ class Form(SignalEvent, TabContent):
in ('tree', 'graph', 'calendar'):
self.screen.search_filter()
self.update_revision()
def get_toolbars(self):
try:
return RPCExecute('model', self.model, 'view_toolbar_get',
context=self.context)
context=self.screen.context)
except RPCException:
return {}
@@ -174,7 +183,7 @@ class Form(SignalEvent, TabContent):
and self.domain == value.domain
and self.mode == value.mode
and self.view_ids == value.view_ids
and self.context == value.context
and self.screen.context == value.screen.context
and self.name == value.name
and self.screen.limit == value.screen.limit
and self.screen.search_value == value.screen.search_value)
@@ -235,7 +244,7 @@ class Form(SignalEvent, TabContent):
try:
res = RPCExecute('model', self.model, 'read', [obj_id],
[x[0] for x in fields], context=self.context)
[x[0] for x in fields], context=self.screen.context)
except RPCException:
return
message_str = ''
@@ -252,6 +261,42 @@ class Form(SignalEvent, TabContent):
message(message_str)
return True
def revision(self, widget=None):
if not self.modified_save():
return
current_id = (self.screen.current_record.id
if self.screen.current_record else None)
try:
revisions = RPCExecute('model', self.model, 'history_revisions',
[r.id for r in self.screen.selected_records])
except RPCException:
return
revision = self.screen.context.get('_datetime')
revision = Revision(revisions, revision).run()
if revision != self.screen.context.get('_datetime'):
self.screen.clear()
# Update root group context that will be propagated
self.screen.group._context['_datetime'] = revision
if self.screen.current_view.view_type != 'form':
self.screen.search_filter(
self.screen.screen_container.get_text())
else:
# Test if record exist in revisions
self.screen.load([current_id])
self.screen.display(set_cursor=True)
self.update_revision()
def update_revision(self):
revision = self.screen.context.get('_datetime')
if revision:
format_ = date_format() + ' %H:%M:%S.%f'
revision = datetime_strftime(revision, format_)
self.title.set_label('%s @ %s' % (self.name, revision))
else:
self.title.set_label(self.name)
for button in ('new', 'save'):
self.buttons[button].props.sensitive = not revision
def sig_remove(self, widget=None):
if not common.MODELACCESS[self.model]['delete']:
return
@@ -271,7 +316,7 @@ class Form(SignalEvent, TabContent):
fields = {}
for name, field in self.screen.group.fields.iteritems():
fields[name] = field.attrs
WinImport(self.model, self.context)
WinImport(self.model, self.screen.context)
def sig_save_as(self, widget=None):
while self.screen.view_to_load:
@@ -279,7 +324,7 @@ class Form(SignalEvent, TabContent):
fields = {}
for name, field in self.screen.group.fields.iteritems():
fields[name] = field.attrs
WinExport(self.model, self.ids_get(), context=self.context)
WinExport(self.model, self.ids_get(), context=self.screen.context)
def sig_new(self, widget=None, autosave=True):
if not common.MODELACCESS[self.model]['create']:
@@ -296,13 +341,7 @@ class Form(SignalEvent, TabContent):
return
if not self.modified_save():
return
ids = [r.id for r in self.screen.selected_records]
try:
new_ids = RPCExecute('model', self.model, 'copy', ids, {},
context=self.context)
except RPCException:
return
self.screen.load(new_ids)
self.screen.copy()
self.message_info(_('Working now on the duplicated record(s)!'),
'green')
@@ -336,11 +375,12 @@ class Form(SignalEvent, TabContent):
self.screen.cancel_current()
set_cursor = False
self.screen.save_tree_state(store=False)
record_id = (self.screen.current_record.id
if self.screen.current_record else None)
if self.screen.current_view.view_type != 'form':
obj_id = self.id_get()
self.screen.search_filter(self.screen.screen_container.get_text())
for record in self.screen.group:
if record.id == obj_id:
if record.id == record_id:
self.screen.current_record = record
set_cursor = True
break
@@ -467,7 +507,7 @@ class Form(SignalEvent, TabContent):
'id': record_id,
'ids': record_ids,
}
Action._exec_action(action, data, self.context)
Action._exec_action(action, data, self.screen.context)
def activate_save(self):
self.buttons['save'].props.sensitive = self.screen.modified()
+68
View File
@@ -0,0 +1,68 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
import gtk
import gettext
from tryton.config import TRYTON_ICON
from tryton.common import get_toplevel_window
from tryton.common.date_widget import DateEntry
from tryton.common.datetime_strftime import datetime_strftime
from tryton.translate import date_format
_ = gettext.gettext
class Revision(object):
'Ask revision'
def __init__(self, revisions, revision=None):
self.parent = get_toplevel_window()
self.win = gtk.Dialog(_('Revision'), self.parent,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK))
self.win.set_default_response(gtk.RESPONSE_OK)
self.win.set_icon(TRYTON_ICON)
self.win.set_has_separator(True)
self.win.vbox.set_spacing(3)
self.win.vbox.pack_start(gtk.Label(
_('Select a revision')), expand=False, fill=True)
self.win.vbox.pack_start(gtk.HSeparator())
hbox = gtk.HBox(spacing=3)
hbox.pack_start(gtk.Label(_('Revision:')), expand=True, fill=True)
format_ = date_format() + ' %H:%M:%S.%f'
self.entry = DateEntry(format_)
if revision:
self.entry.set_text(datetime_strftime(revision, format_))
list_store = gtk.ListStore(str, str)
list_store.append(('', ''))
for revision, id_, name in revisions:
list_store.append((datetime_strftime(revision, format_), name))
combobox = gtk.ComboBoxEntry(list_store)
combobox.add(self.entry)
cell = gtk.CellRendererText()
combobox.pack_start(cell, True)
combobox.add_attribute(cell, 'text', 1)
hbox.pack_start(combobox, expand=True, fill=True)
combobox.set_entry_text_column(0)
combobox.connect('changed', self.changed)
self.entry.set_property('activates_default', True)
self.win.vbox.pack_start(hbox, expand=True, fill=True)
self.win.show_all()
def changed(self, combobox):
# set_text must be called because DateEntry doesn't work correctly with
# the default insert/delete text behavior of combobox
model = combobox.get_model()
idx = combobox.get_active()
if idx >= 0:
self.entry.set_text(model[idx][0])
def run(self):
response = self.win.run()
revision = None
if response == gtk.RESPONSE_OK:
revision = self.entry.date_get()
self.parent.present()
self.win.destroy()
return revision
+1 -1
View File
@@ -43,7 +43,7 @@ class TabContent(object):
self.widget.pack_start(self.scrolledwindow)
def make_title_bar(self):
title = gtk.Label()
self.title = title = gtk.Label()
title.modify_font(pango.FontDescription("bold 14"))
title.set_label(self.name)
title.set_padding(10, 4)
+1 -1
View File
@@ -602,7 +602,7 @@ class Record(SignalEvent):
'search_count', [
('resource', '=',
'%s,%s' % (self.model_name, self.id)),
])
], context=self.context_get())
except RPCException:
return 0
return self.attachment_count
+8 -6
View File
@@ -46,8 +46,6 @@ class Screen(SignalEvent):
view_ids = []
if mode is None:
mode = ['tree', 'form']
if context is None:
context = {}
if views_preload is None:
views_preload = {}
if domain is None:
@@ -69,7 +67,6 @@ class Screen(SignalEvent):
self.size_limit = None
self.views_preload = views_preload
self.model_name = model_name
self.context = context
self.views = []
self.view_ids = view_ids[:]
self.parent = None
@@ -80,7 +77,7 @@ class Screen(SignalEvent):
lambda: collections.defaultdict(lambda: None))
self.tree_states_done = set()
self.__group = None
self.new_group()
self.new_group(context or {})
self.__current_record = None
self.current_record = None
self.screen_container = ScreenContainer(tab_domain)
@@ -230,6 +227,10 @@ class Screen(SignalEvent):
self.load(ids)
return bool(ids)
@property
def context(self):
return self.group.context
def __get_group(self):
return self.__group
@@ -260,9 +261,10 @@ class Screen(SignalEvent):
group = property(__get_group, __set_group)
def new_group(self):
def new_group(self, context=None):
context = context if context is not None else self.context
self.group = Group(self.model_name, {}, domain=self.domain,
context=self.context, readonly=self.readonly)
context=context, readonly=self.readonly)
def _group_cleared(self, group, signal):
for view in self.views:
+12 -7
View File
@@ -122,6 +122,7 @@ class WinForm(NoModal):
hbox = gtk.HBox(homogeneous=False, spacing=0)
tooltips = common.Tooltips()
access = common.MODELACCESS[screen.model_name]
readonly = self.screen.group.readonly
if domain is not None:
self.wid_text = gtk.Entry()
@@ -140,7 +141,7 @@ class WinForm(NoModal):
self.but_add.add(img_add)
self.but_add.set_relief(gtk.RELIEF_NONE)
hbox.pack_start(self.but_add, expand=False, fill=False)
if not access['read']:
if not access['read'] or readonly:
self.but_add.set_sensitive(False)
self.but_remove = gtk.Button()
@@ -153,7 +154,7 @@ class WinForm(NoModal):
self.but_remove.add(img_remove)
self.but_remove.set_relief(gtk.RELIEF_NONE)
hbox.pack_start(self.but_remove, expand=False, fill=False)
if not access['read']:
if not access['read'] or readonly:
self.but_remove.set_sensitive(False)
hbox.pack_start(gtk.VSeparator(), expand=False, fill=True)
@@ -167,7 +168,7 @@ class WinForm(NoModal):
self.but_new.add(img_new)
self.but_new.set_relief(gtk.RELIEF_NONE)
hbox.pack_start(self.but_new, expand=False, fill=False)
if not access['create']:
if not access['create'] or readonly:
self.but_new.set_sensitive(False)
self.but_del = gtk.Button()
@@ -180,7 +181,7 @@ class WinForm(NoModal):
self.but_del.add(img_del)
self.but_del.set_relief(gtk.RELIEF_NONE)
hbox.pack_start(self.but_del, expand=False, fill=False)
if not access['delete']:
if not access['delete'] or readonly:
self.but_del.set_sensitive(False)
self.but_undel = gtk.Button()
@@ -194,6 +195,8 @@ class WinForm(NoModal):
self.but_undel.add(img_undel)
self.but_undel.set_relief(gtk.RELIEF_NONE)
hbox.pack_start(self.but_undel, expand=False, fill=False)
if not access['delete'] or readonly:
self.but_undel.set_sensitive(False)
hbox.pack_start(gtk.VSeparator(), expand=False, fill=True)
@@ -355,9 +358,10 @@ class WinForm(NoModal):
def _sig_label(self, screen, signal_data):
name = '_'
access = common.MODELACCESS[screen.model_name]
readonly = screen.group.readonly
if signal_data[0] >= 1:
name = str(signal_data[0])
self.but_del.set_sensitive(True)
if self.domain is not None:
self.but_remove.set_sensitive(True)
if signal_data[0] < signal_data[1]:
@@ -368,8 +372,9 @@ class WinForm(NoModal):
self.but_pre.set_sensitive(True)
else:
self.but_pre.set_sensitive(False)
self.but_del.set_sensitive(True)
self.but_undel.set_sensitive(True)
if access['delete'] and not readonly:
self.but_del.set_sensitive(True)
self.but_undel.set_sensitive(True)
else:
self.but_del.set_sensitive(False)
self.but_undel.set_sensitive(False)