diff --git a/CHANGELOG b/CHANGELOG index 3e5096a9a8..e0eaf1ee52 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add calendar view * Add URL entry * Remove request diff --git a/COPYRIGHT b/COPYRIGHT index d974e63f83..8121458a0a 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,3 +1,4 @@ +Copyright (C) 2012-2013 Antoine Smolders. Copyright (C) 2010-2013 Nicolas Évrard. Copyright (C) 2007-2013 Cédric Krier. Copyright (C) 2007-2013 Bertrand Chenal. diff --git a/setup.py b/setup.py index 96b5aac914..c0dcd16730 100644 --- a/setup.py +++ b/setup.py @@ -165,6 +165,7 @@ dist = setup(name=PACKAGE, 'timezone': ['pytz'], 'simplejson': ['simplejson'], 'cdecimal': ['cdecimal'], + 'calendar': ['GooCalendar'], }, **args ) diff --git a/tryton/gui/window/view_form/model/record.py b/tryton/gui/window/view_form/model/record.py index 0acab7abd3..59de3b351c 100644 --- a/tryton/gui/window/view_form/model/record.py +++ b/tryton/gui/window/view_form/model/record.py @@ -367,7 +367,7 @@ class Record(SignalEvent): self._check_load() res = True for field_name, field in self.group.fields.iteritems(): - if fields and field_name not in fields: + if fields is not None and field_name not in fields: continue if field.get_state_attrs(self).get('readonly', False): continue diff --git a/tryton/gui/window/view_form/screen/screen.py b/tryton/gui/window/view_form/screen/screen.py index aadef6f7c0..84a96a131f 100644 --- a/tryton/gui/window/view_form/screen/screen.py +++ b/tryton/gui/window/view_form/screen/screen.py @@ -3,6 +3,8 @@ "Screen" import copy import gobject +import datetime +import calendar try: import simplejson as json except ImportError: @@ -185,6 +187,12 @@ class Screen(SignalEvent): else: domain = self.domain + if self.current_view.view_type == 'calendar': + if domain: + domain = ['AND', domain, self.current_view.current_domain()] + else: + domain = self.current_view.current_domain() + tab_domain = self.screen_container.get_tab_domain() if tab_domain: domain = ['AND', domain, tab_domain] @@ -428,12 +436,15 @@ class Screen(SignalEvent): return False def new(self, default=True): + previous_view = self.current_view + if self.current_view.view_type == 'calendar': + selected_date = self.current_view.get_selected_date() if (self.current_view and ((self.current_view.view_type == 'tree' and not (hasattr(self.current_view.widget_tree, 'editable') and self.current_view.widget_tree.editable)) - or self.current_view.view_type == 'graph')): + or self.current_view.view_type in ('graph', 'calendar'))): self.switch_view('form') if self.current_view.view_type != 'form': return None @@ -443,6 +454,8 @@ class Screen(SignalEvent): group = self.group record = group.new(default) group.add(record, self.new_model_position()) + if previous_view.view_type == 'calendar': + previous_view.set_default_date(record, selected_date) self.current_record = record self.display() self.set_cursor(new=True) @@ -674,10 +687,6 @@ class Screen(SignalEvent): else: self.current_record = None if self.views: - #XXX To remove when calendar will be implemented - if self.current_view.view_type == 'calendar' and \ - len(self.views) > 1: - self.switch_view() self.search_active(self.current_view.view_type in ('tree', 'graph', 'calendar')) for view in self.views: @@ -731,6 +740,27 @@ class Screen(SignalEvent): record = next break self.current_record = record + elif view.view_type == 'calendar': + record = self.current_record + goocalendar = view.children['goocalendar'] + date = goocalendar.selected_date + year = date.year + month = date.month + start = datetime.datetime(year, month, 1) + nb_days = calendar.monthrange(year, month)[1] + delta = datetime.timedelta(days=nb_days) + end = start + delta + events = goocalendar.event_store.get_events(start, end) + events.sort() + if not record: + self.current_record = len(events) and events[0].record + else: + for idx, event in enumerate(events): + if event.record == record: + next_id = idx + 1 + if next_id < len(events): + self.current_record = events[next_id].record + break else: self.current_record = self.group[0] if len(self.group) else None self.set_cursor(reset_view=False) @@ -765,6 +795,27 @@ class Screen(SignalEvent): if parent: record = parent self.current_record = record + elif view.view_type == 'calendar': + record = self.current_record + goocalendar = view.children['goocalendar'] + date = goocalendar.selected_date + year = date.year + month = date.month + start = datetime.datetime(year, month, 1) + nb_days = calendar.monthrange(year, month)[1] + delta = datetime.timedelta(days=nb_days) + end = start + delta + events = goocalendar.event_store.get_events(start, end) + events.sort() + if not record: + self.current_record = len(events) and events[0].record + else: + for idx, event in enumerate(events): + if event.record == record: + prev_id = idx - 1 + if prev_id >= 0: + self.current_record = events[prev_id].record + break else: self.current_record = self.group[-1] if len(self.group) else None self.set_cursor(reset_view=False) diff --git a/tryton/gui/window/view_form/view/calendar_.py b/tryton/gui/window/view_form/view/calendar_.py new file mode 100644 index 0000000000..fce0e097ef --- /dev/null +++ b/tryton/gui/window/view_form/view/calendar_.py @@ -0,0 +1,73 @@ +#This file is part of Tryton. The COPYRIGHT file at the top level of +#this repository contains the full copyright notices and license terms. +from functools import wraps + +from interface import ParserView + + +def goocalendar_required(func): + "Decorator for goocalendar required" + @wraps(func) + def wrapper(self, *args, **kwargs): + if 'goocalendar' not in self.children: + return + return func(self, *args, **kwargs) + return wrapper + + +class ViewCalendar(ParserView): + + def __init__(self, screen, widget, children=None, buttons=None, + notebooks=None, cursor_widget=None, children_field=None): + super(ViewCalendar, self).__init__(screen, widget, children, buttons, + notebooks, cursor_widget, children_field) + self.view_type = 'calendar' + + def __getitem__(self, name): + return None + + @goocalendar_required + def destroy(self): + self.widget.destroy() + self.children['goocalendar'].destroy() + + @goocalendar_required + def get_selected_date(self): + return self.children['goocalendar'].selected_date + + @goocalendar_required + def set_default_date(self, record, selected_date): + self.children['goocalendar'].set_default_date(record, selected_date) + + def current_domain(self): + if 'goocalendar' in self.children: + return self.children['goocalendar'].current_domain() + else: + # No need to load any record as nothing will be shown + return [('id', '=', -1)] + + def cancel(self): + pass + + def set_value(self): + pass + + def reset(self): + pass + + @goocalendar_required + def display(self): + self.children['goocalendar'].display(self.screen.group) + gtkcal = self.children['toolbar'].gtkcal + if gtkcal and not gtkcal.is_drawable(): + import goocanvas + # disable gtk.Calendar if it is not drawable anymore + self.children['toolbar'].gtkcal_item.set_property('visibility', + goocanvas.ITEM_INVISIBLE) + self.children['toolbar'].current_page.set_active(False) + + def set_cursor(self, new=False, reset_view=True): + pass + + def get_fields(self): + return [] diff --git a/tryton/gui/window/view_form/view/calendar_gtk/__init__.py b/tryton/gui/window/view_form/view/calendar_gtk/__init__.py new file mode 100644 index 0000000000..5218f22bd6 --- /dev/null +++ b/tryton/gui/window/view_form/view/calendar_gtk/__init__.py @@ -0,0 +1,3 @@ +#This file is part of Tryton. The COPYRIGHT file at the top level of +#this repository contains the full copyright notices and license terms. +from parser import * diff --git a/tryton/gui/window/view_form/view/calendar_gtk/calendar_.py b/tryton/gui/window/view_form/view/calendar_gtk/calendar_.py new file mode 100644 index 0000000000..0b2c334168 --- /dev/null +++ b/tryton/gui/window/view_form/view/calendar_gtk/calendar_.py @@ -0,0 +1,120 @@ +#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 calendar +import datetime +import goocalendar +import tryton.common as common +from dates_period import DatesPeriod + + +class Calendar_(goocalendar.Calendar): + 'Calendar' + + def __init__(self, attrs, model, root_node, fields, screen, + event_store=None): + super(Calendar_, self).__init__(event_store) + self.attrs = attrs + self.model = model + self.root_node = root_node + self.fields = fields + self.screen = screen + self.event_store = event_store + self.current_domain_period = self.get_displayed_period() + + def destroy(self): + super(Calendar_, self).destroy() + + def set_default_date(self, record, selected_date): + attrs = common.node_attributes(self.root_node) + dtstart = attrs.get('dtstart') + record[dtstart].set(record, datetime.datetime.combine(selected_date, + datetime.time(0))) + + def get_displayed_period(self): + cal = calendar.Calendar(self.firstweekday) + if self.view == 'week': + week = goocalendar.util.my_weekdatescalendar(cal, + self.selected_date) + first_date = week[0] + last_date = week[6] + last_date += datetime.timedelta(1) + elif self.view == 'month': + weeks = goocalendar.util.my_monthdatescalendar(cal, + self.selected_date) + first_date = weeks[0][0] + last_date = weeks[5][6] + last_date += datetime.timedelta(1) + displayed_period = DatesPeriod(first_date, last_date) + return displayed_period + + def update_domain(self): + displayed_period = self.get_displayed_period() + if not displayed_period.is_in(self.current_domain_period): + self.current_domain_period = displayed_period + return True + return False + + def current_domain(self): + first_datetime, last_datetime = \ + self.current_domain_period.get_dates(True) + attrs = common.node_attributes(self.root_node) + dtstart = attrs.get('dtstart') + dtend = attrs.get('dtend') or dtstart + domain = ['OR', + ['AND', (dtstart, '>=', first_datetime), + (dtstart, '<', last_datetime)], + ['AND', (dtend, '>=', first_datetime), + (dtend, '<', last_datetime)], + ['AND', (dtstart, '<', first_datetime), + (dtend, '>', last_datetime)]] + return domain + + def display(self, group): + attrs = common.node_attributes(self.root_node) + dtstart = attrs.get('dtstart') + dtend = attrs.get('dtend') or dtstart + if self.screen.current_record: + record = self.screen.current_record + date = record[dtstart].get(record) + if date: # select the day of the current record + self.select(date) + + if self._event_store: + self._event_store.clear() + else: + event_store = goocalendar.EventStore() + self.event_store = event_store + + fields = [] + for node in self.root_node.childNodes: + if not node.nodeType == node.ELEMENT_NODE: + continue + if node.localName == 'field': + attrs = common.node_attributes(node) + fields.append(attrs.get('name')) + + for record in group: + if not record[dtstart].get(record): + continue + + start = record[dtstart].get(record) + end = record[dtend].get(record) + midnight = datetime.time(0) + all_day = False + if not isinstance(start, datetime.datetime): + start = datetime.datetime.combine(start, midnight) + if end and not isinstance(end, datetime.datetime): + end = datetime.datetime.combine(end, midnight) + all_day = True + elif not end: + all_day = True + + # TODO define color code + label = '\n'.join(record[name].get_client(record) + for name in fields).rstrip() + event = goocalendar.Event(label, start, end, + bg_color='lightblue', all_day=all_day) + event.record = record + self._event_store.add(event) + + self.grab_focus(self.get_root_item()) diff --git a/tryton/gui/window/view_form/view/calendar_gtk/dates_period.py b/tryton/gui/window/view_form/view/calendar_gtk/dates_period.py new file mode 100644 index 0000000000..becb3bda5e --- /dev/null +++ b/tryton/gui/window/view_form/view/calendar_gtk/dates_period.py @@ -0,0 +1,31 @@ +#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 datetime + + +class DatesPeriod(): + """ + This class represents a period of time between two dates or two datetimes. + """ + + def __init__(self, start, end): + assert type(start) == type(end) + self.start = start + self.end = end + + def is_in(self, period): + return self.start >= period.start \ + and self.end <= period.end + + def get_dates(self, format_datetime=False): + if not format_datetime: + return self.start, self.end + + midnight = datetime.time(0) + start = datetime.datetime.combine(self.start, midnight) + end = datetime.datetime.combine(self.end, midnight) + return start, end + + def __str__(self): + string = self.start.__str__() + ' => ' + self.end.__str__() + return string diff --git a/tryton/gui/window/view_form/view/calendar_gtk/parser.py b/tryton/gui/window/view_form/view/calendar_gtk/parser.py new file mode 100644 index 0000000000..579f05972e --- /dev/null +++ b/tryton/gui/window/view_form/view/calendar_gtk/parser.py @@ -0,0 +1,85 @@ +#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 datetime +import gettext +import gtk +from tryton.gui.window.view_form.view.interface import ParserInterface +import tryton.common as common +try: + from calendar_ import Calendar_ + from toolbar import Toolbar +except ImportError: + Calendar_ = None + Toolbar = None + +_ = gettext.gettext + + +class ParserCalendar(ParserInterface): + + def on_page_changed(self, goocalendar, day, toolbar): + toolbar.update_displayed_date() + if goocalendar.update_domain(): + self.screen.search_filter() + + def on_view_changed(self, goocalendar, view, toolbar): + toolbar.update_displayed_date() + if goocalendar.update_domain(): + self.screen.search_filter() + + def on_event_pressed(self, goocalendar, event): + self.screen.current_record = event.record + + def on_event_activated(self, goocalendar, event): + self.screen.switch_view('form') + + def on_event_released(self, goocalendar, event, attrs): + dtstart = attrs.get('dtstart') + dtend = attrs.get('dtend') or dtstart + record = event.record + group = record.group + previous_start = record[dtstart].get(record) + new_start = event.start + new_end = event.end + if not isinstance(previous_start, datetime.datetime): + new_start = event.start.date() + new_end = event.end.date() if event.end else None + if previous_start <= new_start: + if new_end: + group.fields[dtend].set_client(record, new_end) + group.fields[dtstart].set_client(record, new_start) + else: + group.fields[dtstart].set_client(record, new_start) + if new_end: + group.fields[dtend].set_client(record, new_end) + goocalendar.select(new_start) + record.save() + + def on_day_pressed(self, goocalendar, day): + self.screen.current_record = None + + def on_day_activated(self, goocalendar, day): + self.screen.new() + + def parse(self, model, root_node, fields): + attrs = common.node_attributes(root_node) + self.title = attrs.get('string', 'Unknown') + vbox = gtk.VBox() + if not Calendar_: + return vbox, {}, [], '', [], None + goocalendar = Calendar_(attrs=attrs, model=model, root_node=root_node, + fields=fields, screen=self.screen) + toolbar = Toolbar(goocalendar) + goocalendar.connect('view-changed', self.on_view_changed, toolbar) + goocalendar.connect('page-changed', self.on_page_changed, toolbar) + goocalendar.connect('event-pressed', self.on_event_pressed) + goocalendar.connect('event-activated', self.on_event_activated) + goocalendar.connect('event-released', self.on_event_released, attrs) + goocalendar.connect('day-pressed', self.on_day_pressed) + goocalendar.connect('day-activated', self.on_day_activated) + scrolledWindow = gtk.ScrolledWindow() + scrolledWindow.add_with_viewport(goocalendar) + vbox.pack_start(toolbar, False, False) + vbox.pack_start(scrolledWindow, True, True) + return vbox, {'root': scrolledWindow, 'toolbar': toolbar, + 'goocalendar': goocalendar}, [], '', [], None diff --git a/tryton/gui/window/view_form/view/calendar_gtk/toolbar.py b/tryton/gui/window/view_form/view/calendar_gtk/toolbar.py new file mode 100644 index 0000000000..30efd2f335 --- /dev/null +++ b/tryton/gui/window/view_form/view/calendar_gtk/toolbar.py @@ -0,0 +1,176 @@ +#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 datetime +import calendar +import gettext +import gtk +import goocanvas +from tryton.gui import Main + +_ = gettext.gettext + + +class Toolbar(gtk.Toolbar): + + def __init__(self, goocalendar): + super(Toolbar, self).__init__() + self.goocalendar = goocalendar + self.accel_group = Main.get_main().accel_group + + today_label = gtk.Label(_('Today')) + today_button = gtk.ToolButton(today_label) + today_button.set_homogeneous(False) + today_button.connect("clicked", self.on_today_button_clicked) + today_button.add_accelerator("clicked", self.accel_group, + gtk.keysyms.t, gtk.gdk.MODIFIER_MASK, gtk.ACCEL_VISIBLE) + self.insert(today_button, -1) + + arrow_left = gtk.Arrow(gtk.ARROW_LEFT, gtk.SHADOW_NONE) + go_back = gtk.ToolButton(arrow_left, "go left") + go_back.set_expand(False) + go_back.set_homogeneous(False) + go_back.connect("clicked", self.on_go_back_clicked) + self.insert(go_back, -1) + + self.current_page_label = gtk.Label("") + self.current_page = gtk.ToggleToolButton() + self.current_page.set_icon_widget(self.current_page_label) + self.current_page.connect("clicked", self.on_current_page_clicked) + self.insert(self.current_page, -1) + + gtkcal = gtk.Calendar() + gtkcal.connect('day-selected', self.on_gtkcal_day_selected) + gtkcal.set_display_options( + gtk.CALENDAR_SHOW_HEADING | + gtk.CALENDAR_SHOW_WEEK_NUMBERS | + gtk.CALENDAR_SHOW_DAY_NAMES) + gtkcal.set_no_show_all(True) + gtkcal_item = goocanvas.Widget(widget=gtkcal) + gtkcal_item.set_property('visibility', goocanvas.ITEM_INVISIBLE) + goocalendar.get_root_item().add_child(gtkcal_item) + self.gtkcal = gtkcal + self.gtkcal_item = gtkcal_item + self.goocalendar.connect('day-selected', + self.on_goocalendar_day_selected) + + arrow_right = gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_NONE) + go_forward = gtk.ToolButton(arrow_right, "go right") + go_forward.set_expand(False) + go_forward.set_homogeneous(False) + go_forward.connect("clicked", self.on_go_forward_clicked) + self.insert(go_forward, -1) + + arrow_left = gtk.Arrow(gtk.ARROW_LEFT, gtk.SHADOW_NONE) + previous_year = gtk.ToolButton(arrow_left, "next year") + previous_year.set_expand(False) + previous_year.set_homogeneous(False) + previous_year.connect("clicked", self.on_previous_year_clicked) + self.insert(previous_year, -1) + + self.current_year_label = gtk.Label("") + current_year = gtk.ToolItem() + current_year.add(self.current_year_label) + self.insert(current_year, -1) + + arrow_right = gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_NONE) + next_year = gtk.ToolButton(arrow_right, "next year") + next_year.set_expand(False) + next_year.set_homogeneous(False) + next_year.connect("clicked", self.on_next_year_clicked) + self.insert(next_year, -1) + + blank_widget = gtk.ToolItem() + blank_widget.set_expand(True) + self.insert(blank_widget, -1) + + week_button = gtk.RadioToolButton() + week_button.set_label(_('Week View')) + week_button.connect("clicked", self.on_week_button_clicked) + week_button.add_accelerator("clicked", self.accel_group, gtk.keysyms.w, + gtk.gdk.MODIFIER_MASK, gtk.ACCEL_VISIBLE) + self.insert(week_button, -1) + + month_view_label = gtk.Label(_('Month View')) + month_button = gtk.RadioToolButton(week_button) + month_button.set_icon_widget(month_view_label) + month_button.set_label(_('month_view')) + month_button.connect("clicked", self.on_month_button_clicked) + month_button.add_accelerator("clicked", self.accel_group, + gtk.keysyms.m, gtk.gdk.MODIFIER_MASK, gtk.ACCEL_VISIBLE) + month_button.set_active(True) + self.insert(month_button, -1) + self.update_displayed_date() + self.set_style(gtk.TOOLBAR_ICONS) + + def update_displayed_date(self): + date = self.goocalendar.selected_date + year = date.timetuple()[0] + month = date.timetuple()[1] + day = date.timetuple()[2] + self.current_year_label.set_text(str(year)) + + if self.goocalendar.view == "month": + new_label = calendar.month_name[month] + self.current_page_label.set_text(new_label) + elif self.goocalendar.view == "week": + week_number = datetime.date(year, month, day).isocalendar()[1] + new_label = _('Week') + ' ' + str(week_number) + new_label += ' (' + calendar.month_name[month] + ')' + self.current_page_label.set_text(new_label) + + def on_today_button_clicked(self, widget): + self.goocalendar.select(datetime.date.today()) + + def on_go_back_clicked(self, widget): + self.goocalendar.previous_page() + + def on_current_page_clicked(self, widget): + gtkcal_item = self.gtkcal_item + if gtkcal_item.get_property('visibility') == goocanvas.ITEM_VISIBLE: + gtkcal_item.set_property('visibility', goocanvas.ITEM_INVISIBLE) + else: + gtkcal_item.set_property('visibility', goocanvas.ITEM_VISIBLE) + + def on_gtkcal_day_selected(self, gtkcal): + year, month, day = gtkcal.get_date() + month += 1 # months go from 1 to 12 instead of from 0 to 11 + self.goocalendar.select(datetime.date(year, month, day)) + + def on_goocalendar_day_selected(self, goocalendar, day): + # months go from 0 to 11 in gtk.Calendar instead of 1 to 12 + new_date = self.goocalendar.selected_date + self.gtkcal.select_month(new_date.month - 1, new_date.year) + self.gtkcal.handler_block_by_func(self.on_gtkcal_day_selected) + self.gtkcal.select_day(new_date.day) + self.gtkcal.handler_unblock_by_func(self.on_gtkcal_day_selected) + + def on_go_forward_clicked(self, widget): + self.goocalendar.next_page() + + def on_previous_year_clicked(self, widget): + date = datetime.datetime.combine(self.goocalendar.selected_date, + datetime.time(0)) + year, month, day = date.timetuple()[:3] + year -= 1 + cal = calendar.Calendar(self.goocalendar.firstweekday) + next_month_days = [d for d in cal.itermonthdays(year, month)] + if day not in next_month_days: + day = max(next_month_days) + self.goocalendar.select(datetime.datetime(year, month, day)) + + def on_next_year_clicked(self, widget): + date = datetime.datetime.combine(self.goocalendar.selected_date, + datetime.time(0)) + year, month, day = date.timetuple()[:3] + year += 1 + cal = calendar.Calendar(self.goocalendar.firstweekday) + next_month_days = [d for d in cal.itermonthdays(year, month)] + if day not in next_month_days: + day = max(next_month_days) + self.goocalendar.select(datetime.datetime(year, month, day)) + + def on_week_button_clicked(self, widget): + self.goocalendar.set_view("week") + + def on_month_button_clicked(self, widget): + self.goocalendar.set_view("month") diff --git a/tryton/gui/window/view_form/view/widget_parse.py b/tryton/gui/window/view_form/view/widget_parse.py index 4ed4c024cd..45a33112a3 100644 --- a/tryton/gui/window/view_form/view/widget_parse.py +++ b/tryton/gui/window/view_form/view/widget_parse.py @@ -4,32 +4,31 @@ from interface import ParserInterface import form_gtk import list_gtk import graph_gtk -#import calendar_gtk +import calendar_gtk from form import ViewForm from list import ViewList from graph import ViewGraph -#from calendar import ViewCalendar +from calendar_ import ViewCalendar from tryton.exceptions import TrytonError PARSERS = { 'form': form_gtk.ParserForm, 'tree': list_gtk.ParserTree, 'graph': graph_gtk.ParserGraph, - #'calendar': calendar_gtk.parser_calendar, -} + 'calendar': calendar_gtk.ParserCalendar, + } PARSERS2 = { 'form': ViewForm, 'tree': ViewList, 'graph': ViewGraph, - #'calendar': ViewCalendar, -} + 'calendar': ViewCalendar, + } class WidgetParse(ParserInterface): def parse(self, screen, root_node, fields, children_field=None): - widget = None for node in root_node.childNodes: if not node.nodeType == node.ELEMENT_NODE: continue @@ -43,8 +42,6 @@ class WidgetParse(ParserInterface): res = PARSERS2[node.localName](screen, wid, child, state_widgets, notebooks, cursor_widget, children_field) res.title = widget.title - widget = res - break + return res else: raise TrytonError('Unknow view mode: %s' % node.localName) - return widget