Beispiel #1
0
 def set_value(self, value):
     if value:
         self.relation, self.default = value.split(",")
         self.text = rpc.name_get(self.relation, self.default, rpc.session.context)
     else:
         self.relation = ''
         self.default = ''
         self.text = ''
Beispiel #2
0
    def update_params(self, params):
        super(M2O, self).update_params(params)

        if params['value'] and not params['text']:
            try:
                value = expr_eval(params['value'], {'context':rpc.session.context})
            except:
                value = params['value']
            params['text'] = rpc.name_get(self.relation, value, rpc.session.context)
Beispiel #3
0
    def set_value(self, value):

        if value and isinstance(value, (tuple, list)) and len(value) == 2:
            self.default, self.text = value
        elif value and isinstance(value, basestring):
            self.text = value
        else:
            self.default = value
            self.text = rpc.name_get(self.relation, self.default, rpc.session.context)
Beispiel #4
0
 def set_value(self, value):
     if value:
         self.relation, self.default = value.split(",")
         self.text = rpc.name_get(self.relation, self.default,
                                  rpc.session.context)
     else:
         self.relation = ''
         self.default = ''
         self.text = ''
Beispiel #5
0
 def get_text(self):
     self.refrel = False
     self.refid = False
     if isinstance(self.value, dict):
         self.value = self.value.get('selection',"")
     if self.value and ',' in self.value:
         self.refrel, self.refid = self.value.split(',', 2)
         self.value = rpc.name_get(self.refrel, self.refid, rpc.session.context)
         return self.value
     return ''
    def set_value(self, value):

        if value and isinstance(value, (tuple, list)) and len(value) == 2:
            self.default, self.text = value
        elif value and isinstance(value, basestring):
            self.text = value
        else:
            self.default = value
            self.text = rpc.name_get(self.relation, self.default,
                                     rpc.session.context)
    def update_params(self, params):
        super(M2O, self).update_params(params)

        if params['value'] and not params['text']:
            try:
                value = expr_eval(params['value'],
                                  {'context': rpc.session.context})
            except:
                value = params['value']
            params['text'] = rpc.name_get(self.relation, value,
                                          rpc.session.context)
    def get_text(self):

        if isinstance(self.value, (int, long)):
            self.value = self.value, rpc.name_get(self.attrs['relation'], self.value, rpc.session.context)

        if self.value and len(self.value) > 0:
            if isinstance(self.value, (tuple, list)):
                return self.value[-1]
            else:
                return self.value

        return ''
Beispiel #9
0
 def set_value(self, value):
     if isinstance(value, dict):
         if value.get('options'):
             self.options = value['options']
         value = value.get('selection',"")
     if value:
         self.relation, self.default = value.split(",")
         self.text = rpc.name_get(self.relation, self.default, rpc.session.context)
     else:
         self.relation = ''
         self.default = ''
         self.text = ''
Beispiel #10
0
    def get_text(self):

        if isinstance(self.value, (int, long)):
            self.value = self.value, rpc.name_get(self.attrs['relation'],
                                                  self.value,
                                                  rpc.session.context)

        if self.value and len(self.value) > 0:
            if isinstance(self.value, (tuple, list)):
                return self.value[-1]
            else:
                return self.value

        return ''
Beispiel #11
0
 def get_name(self, model, id):
     return dict(name=rpc.name_get(model, id, rpc.session.context))
Beispiel #12
0
    def parse(self, model=None, root=None, fields=None, values={}):

        views = []
        search_model = model

        filters_run = []
        for node in root.childNodes:
            if not node.nodeType==node.ELEMENT_NODE:
                continue

            if filters_run and node.localName != 'filter':
                views.append(FiltersGroup(children=filters_run))
                filters_run = []

            attrs = node_attributes(node)
            attrs.update(is_search=True,
                         model=search_model)

            if 'nolabel' in attrs:
                attrs['nolabel'] = False

            if node.localName in ('form', 'tree', 'search', 'group'):
                if node.localName == 'group':
                    attrs['group_by_ctx'] = values.get('group_by_ctx')
                    attrs['expand'] = expr_eval(attrs.get('expand',False),{'context':self.context})
                    Element = Group
                else:
                    Element = Frame

                views.append(Element(children=
                                     self.parse(model=search_model, root=node,
                                                fields=fields, values=values),
                                     **attrs))

            elif node.localName=='newline':
                views.append(NewLine(**attrs))

            elif node.localName=='filter':
                attrs.update(
                    model=search_model,
                    default_domain=self.domain,
                    screen_context=self.context)
                if values and values.get('group_by_ctx'):
                    attrs['group_by_ctx'] = values['group_by_ctx']
                elif self.groupby:
                    attrs['group_by_ctx'] = self.groupby

                v = Filter(**attrs)
                if v.groupcontext and v.groupcontext not in self.groupby:
                    self.groupby.append(v.groupcontext)
                self.listof_domain.extend(i for i in v.global_domain if not i in self.listof_domain)
                filters_run.append(v)

            elif node.localName == 'field':
                val  = attrs.get('select', False) or fields[str(attrs['name'])].get('select', False) or self.view_type == 'search'
                if val:
                    name = attrs['name']
                    if name in self.fields_type:
                        continue

                    # in search view fields should be writable
                    attrs.update(readonly=False,
                                 required=False,
                                 translate=False,
                                 disabled=False,
                                 visible=True,
                                 invisible=False,
                                 editable=True,
                                 attrs=None)

                    try:
                        fields[name].update(attrs)
                    except:
                        print "-"*30,"\n malformed tag for:", attrs
                        print "-"*30
                        raise

                    if attrs.get('widget'):
                        if attrs['widget'] == 'one2many_list':
                            fields[name]['widget'] = 'one2many'

                        fields[name]['type2'] = fields[name]['type']
                        fields[name]['type'] = attrs['widget']

                    kind = fields[name]['type']

                    if kind not in WIDGETS:
                        continue

                    if kind == 'many2one':
                        attrs['relation'] = fields[name]['relation']
                        attrs['type'] = fields[name]['type']
                        if not attrs.get('string'):
                            attrs['string'] = fields[name]['string']

                    self.fields_type[name] = kind

                    field = WIDGETS[kind](**fields[name])
                    field.onchange = None
                    field.callback = None

                    if kind == 'boolean':
                        # '0' in string because 0 is considering as No Value, and we need 0 as some value.
                        field.options = [[1,_('Yes')],['0',_('No')]]
                        field.validator.if_empty = ''

                    default_search = None
                    if name:
                        default_search = get_search_default(fields[name], self.context, self.domain)
                        defval = default_search
                        if defval:
                            model = fields[name].get('relation')
                            type2 = fields[name].get('type2')

                            if kind == 'many2one' and model:
                                try:
                                    value = rpc.name_get(model, default_search, self.context)
                                except Exception,e:
                                    value = defval
                                defval = value or ''

                            if attrs.get('filter_domain'):
                                domain = expr_eval(attrs['filter_domain'], {'self': defval})
                            else:
                                if field.kind == 'selection':
                                    if type2 == 'many2one':
                                        m2nval = defval
                                        try:
                                            m2nval = int(defval)
                                        except:
                                            # you see, since defval can basically be anything, including a totally
                                            # illogic value, my only possibility is to do return another illogic value,
                                            # so it won't crash too much
                                            pass
                                        domain = [(name, '=', m2nval)]
                                    else:
                                        domain = [(name, '=', defval)]

                                elif field.kind in ('date','datetime'):
                                    domain = [(name, '>=', defval)]
                                    
                                elif field.kind == 'boolean':
                                    domain = [(name, '=', defval)]
                                               
                                else:
                                    domain = [(name,fields[name].get('comparator','ilike'), defval)]

                            field.set_value(defval)
                            self.listof_domain += [i for i in domain if not i in self.listof_domain]
                            self.context.update(expr_eval(attrs.get('context',"{}"), {'self': default_search}))

                    if (not default_search) and name in values and isinstance(field, TinyInputWidget):
                        field.set_value(values[name])

                    views.append(field)
                    for n in node.childNodes:
                        if n.localName=='filter':
                            attrs_child = dict(
                                node_attributes(n),
                                default_domain=self.domain,
                                screen_context=self.context)
                            if 'string' in attrs_child: del attrs_child['string']
                            if values and values.get('group_by_ctx'):
                                attrs['group_by_ctx'] = values['group_by_ctx']

                            filter_field = Filter(**attrs_child)
                            filter_field.onchange = None
                            filter_field.callback = None

                            if filter_field.groupcontext and filter_field.groupcontext not in self.groupby:
                                self.groupby.append(filter_field.groupcontext)
                            self.listof_domain.extend(i for i in filter_field.global_domain
                                                        if i not in self.listof_domain)
                            field.filters.append(filter_field)
Beispiel #13
0
    def parse(self, model=None, root=None, fields=None, values={}):

        views = []
        search_model = model

        filters_run = []
        for node in root.childNodes:
            if not node.nodeType == node.ELEMENT_NODE:
                continue

            if filters_run and node.localName != 'filter':
                views.append(FiltersGroup(children=filters_run))
                filters_run = []

            attrs = node_attributes(node)
            attrs.update(is_search=True, model=search_model)

            if 'nolabel' in attrs:
                attrs['nolabel'] = False

            if node.localName in ('form', 'tree', 'search', 'group'):
                if node.localName == 'group':
                    attrs['group_by_ctx'] = values.get('group_by_ctx')
                    attrs['expand'] = expr_eval(attrs.get('expand', False),
                                                {'context': self.context})
                    Element = Group
                else:
                    Element = Frame

                views.append(
                    Element(children=self.parse(model=search_model,
                                                root=node,
                                                fields=fields,
                                                values=values),
                            **attrs))

            elif node.localName == 'newline':
                views.append(NewLine(**attrs))

            elif node.localName == 'filter':
                attrs.update(model=search_model,
                             default_domain=self.domain,
                             screen_context=self.context)
                if values and values.get('group_by_ctx'):
                    attrs['group_by_ctx'] = values['group_by_ctx']
                elif self.groupby:
                    attrs['group_by_ctx'] = self.groupby

                if values and values.get('filter_status'):
                    attrs['filter_status'] = values['filter_status']

                v = Filter(**attrs)
                if v.groupcontext and v.groupcontext not in self.groupby:
                    self.groupby.append(v.groupcontext)
                self.listof_domain.extend(i for i in v.global_domain
                                          if not i in self.listof_domain)
                filters_run.append(v)

            elif node.localName == 'field':
                val = attrs.get(
                    'select', False) or fields[str(attrs['name'])].get(
                        'select', False) or self.view_type == 'search'
                if val:
                    name = attrs['name']
                    if name in self.fields_type:
                        continue

                    # in search view fields should be writable
                    attrs.update(readonly=False,
                                 required=False,
                                 translate=False,
                                 disabled=False,
                                 visible=True,
                                 invisible=False,
                                 editable=True,
                                 attrs=None)

                    try:
                        fields[name].update(attrs)
                    except:
                        print "-" * 30, "\n malformed tag for:", attrs
                        print "-" * 30
                        raise

                    if attrs.get('widget'):
                        if attrs['widget'] == 'one2many_list':
                            fields[name]['widget'] = 'one2many'

                        fields[name]['type2'] = fields[name]['type']
                        fields[name]['type'] = attrs['widget']

                    kind = fields[name]['type']

                    if kind not in WIDGETS:
                        continue

                    if kind == 'many2one':
                        attrs['relation'] = fields[name]['relation']
                        attrs['type'] = fields[name]['type']
                        if not attrs.get('string'):
                            attrs['string'] = fields[name]['string']

                    self.fields_type[name] = kind

                    field = WIDGETS[kind](**fields[name])
                    field.onchange = None
                    field.callback = None

                    if kind == 'boolean':
                        # '0' in string because 0 is considering as No Value, and we need 0 as some value.
                        field.options = [[1, _('Yes')], ['0', _('No')]]
                        field.validator.if_empty = ''

                    default_search = None
                    if name:
                        default_search = get_search_default(
                            fields[name], self.context, self.domain)
                        defval = default_search
                        if defval:
                            model = fields[name].get('relation')
                            type2 = fields[name].get('type2')

                            if kind == 'many2one' and model:
                                try:
                                    value = rpc.name_get(
                                        model, default_search, self.context)
                                except Exception, e:
                                    value = defval
                                defval = value or ''

                            if attrs.get('filter_domain'):
                                domain = expr_eval(attrs['filter_domain'],
                                                   {'self': defval})
                            else:
                                if field.kind == 'selection':
                                    if type2 == 'many2one':
                                        m2nval = defval
                                        try:
                                            m2nval = int(defval)
                                        except:
                                            # you see, since defval can basically be anything, including a totally
                                            # illogic value, my only possibility is to do return another illogic value,
                                            # so it won't crash too much
                                            pass
                                        domain = [(name, '=', m2nval)]
                                    else:
                                        domain = [(name, '=', defval)]

                                elif field.kind in ('date', 'datetime'):
                                    domain = [(name, '>=', defval)]

                                elif field.kind == 'boolean':
                                    domain = [(name, '=', defval)]

                                else:
                                    domain = [(name, fields[name].get(
                                        'comparator', 'ilike'), defval)]

                            field.set_value(defval)
                            self.listof_domain += [
                                i for i in domain
                                if not i in self.listof_domain
                            ]
                            self.context.update(
                                expr_eval(attrs.get('context', "{}"),
                                          {'self': default_search}))

                    if (not default_search) and name in values and isinstance(
                            field, TinyInputWidget):
                        field.set_value(values[name])

                    views.append(field)
                    for n in node.childNodes:
                        if n.localName == 'filter':
                            attrs_child = dict(node_attributes(n),
                                               default_domain=self.domain,
                                               screen_context=self.context)
                            if 'string' in attrs_child:
                                del attrs_child['string']
                            if values and values.get('group_by_ctx'):
                                attrs['group_by_ctx'] = values['group_by_ctx']

                            filter_field = Filter(**attrs_child)
                            filter_field.onchange = None
                            filter_field.callback = None

                            if filter_field.groupcontext and filter_field.groupcontext not in self.groupby:
                                self.groupby.append(filter_field.groupcontext)
                            self.listof_domain.extend(
                                i for i in filter_field.global_domain
                                if i not in self.listof_domain)
                            field.filters.append(filter_field)
Beispiel #14
0
class Form(SecuredController):

    _cp_path = "/openerp/form"

    def create_form(self, params, tg_errors=None):
        if tg_errors:
            return cherrypy.request.terp_form

        cherrypy.session['params'] = params

        params.offset = params.offset or 0
        params.limit = params.limit or 50
        params.count = params.count or 0
        params.view_type = params.view_type or params.view_mode[0]

        return tw.form_view.ViewForm(params,
                                     name="view_form",
                                     action="/openerp/form/save")

    @expose(template="/openerp/controllers/templates/form.mako")
    def create(self, params, tg_errors=None):

        params.view_type = params.view_type or params.view_mode[0]

        if params.view_type == 'tree':
            params.editable = True
        form = self.create_form(params, tg_errors)

        if not tg_errors:
            try:
                cherrypy.session.pop('remember_notebooks')
            except:
                self.reset_notebooks()

        editable = form.screen.editable
        mode = form.screen.view_type
        id = form.screen.id
        buttons = TinyDict()  # toolbar
        buttons.new = (not editable or mode == 'tree') and mode != 'diagram'
        buttons.edit = not editable and (mode == 'form' or mode == 'diagram')
        buttons.save = editable and mode == 'form'
        buttons.cancel = editable and mode == 'form'
        buttons.delete = not editable and mode == 'form'
        buttons.pager = mode == 'form' or mode == 'diagram'  # Pager will visible in edit and non-edit mode in form view.
        buttons.can_attach = id and mode == 'form'
        buttons.i18n = not editable and mode == 'form'
        buttons.show_grid = mode == 'diagram'
        buttons.create_node = mode == 'diagram' and editable

        from openerp.widgets import get_registered_views
        buttons.views = []

        for kind, view in get_registered_views():
            buttons.views.append(
                dict(kind=kind, name=view.name, desc=view.desc))

        target = getattr(cherrypy.request, '_terp_view_target', None)
        buttons.toolbar = (target != 'new'
                           and not form.is_dashboard) or mode == 'diagram'
        pager = None
        if buttons.pager:
            pager = tw.pager.Pager(id=form.screen.id,
                                   ids=form.screen.ids,
                                   offset=form.screen.offset,
                                   limit=form.screen.limit,
                                   count=form.screen.count,
                                   view_type=params.view_type)

        can_shortcut = self.can_shortcut_create()
        shortcut_ids = []

        if cherrypy.session.get('terp_shortcuts'):
            for sc in cherrypy.session['terp_shortcuts']:
                if isinstance(sc['res_id'], tuple):
                    shortcut_ids.append(sc['res_id'][0])
                else:
                    shortcut_ids.append(sc['res_id'])

        title = form.screen.string or ''
        display_name = {}
        if params.view_type == 'form':
            if params.id:
                if form.screen.view.get(
                        'fields') and form.screen.view['fields'].get('name'):
                    display_name = {
                        'field': form.screen.view['fields']['name']['string'],
                        'value':
                        ustr(form.screen.view['fields']['name']['value'])
                    }
                    title = ustr(display_name['field']) + ':' + ustr(
                        display_name['value'])
        elif params.view_type == 'diagram':
            display_name = {
                'field':
                form.screen.view['fields']['name']['string'],
                'value':
                rpc.RPCProxy(params.model).name_get(form.screen.id,
                                                    rpc.session.context)[0][1]
            }

        # For Corporate Intelligence visibility.
        obj_process = rpc.RPCProxy('ir.model').search(
            [('model', '=', 'process.process')]) or None

        tips = params.display_menu_tip
        if params.view_type == params.view_mode[0] and tips:
            tips = tips

        is_dashboard = form.screen.is_dashboard or False
        return dict(form=form,
                    pager=pager,
                    buttons=buttons,
                    path=self.path,
                    can_shortcut=can_shortcut,
                    shortcut_ids=shortcut_ids,
                    display_name=display_name,
                    title=title,
                    tips=tips,
                    obj_process=obj_process,
                    is_dashboard=is_dashboard)

    @expose('json', methods=('POST', ))
    def close_or_disable_tips(self):
        rpc.RPCProxy('res.users').write(rpc.session.uid, {'menu_tips': False},
                                        rpc.session.context)

    def _read_form(self,
                   context,
                   count,
                   domain,
                   filter_domain,
                   id,
                   ids,
                   kw,
                   limit,
                   model,
                   offset,
                   search_data,
                   search_domain,
                   source,
                   view_ids,
                   view_mode,
                   view_type,
                   notebook_tab,
                   o2m_edit=False,
                   editable=False):
        """ Extract parameters for form reading/creation common to both
        self.edit and self.view
        """
        params, data = TinyDict.split({
            '_terp_model': model,
            '_terp_id': id,
            '_terp_ids': ids,
            '_terp_view_ids': view_ids,
            '_terp_view_mode': view_mode,
            '_terp_view_type': view_type,
            '_terp_source': source,
            '_terp_domain': domain,
            '_terp_context': context,
            '_terp_offset': offset,
            '_terp_limit': limit,
            '_terp_count': count,
            '_terp_search_domain': search_domain,
            '_terp_search_data': search_data,
            '_terp_filter_domain': filter_domain,
            '_terp_notebook_tab': notebook_tab
        })
        params.o2m_edit = o2m_edit
        params.editable = editable
        params.action_id = kw.get('action_id')

        if kw.get('default_date'):
            params.context.update({'default_date': kw['default_date']})

        cherrypy.request._terp_view_target = kw.get('target')

        if params.view_mode and 'form' not in params.view_mode:
            params.view_type = params.view_mode[-1]

        if params.view_type == 'tree':
            params.view_type = 'form'

        if not params.ids:
            params.offset = 0

        return params

    @expose()
    def edit(self,
             model,
             id=False,
             ids=None,
             view_ids=None,
             view_mode=['form', 'tree'],
             view_type='form',
             source=None,
             domain=[],
             context={},
             offset=0,
             limit=50,
             count=0,
             search_domain=None,
             search_data=None,
             filter_domain=None,
             o2m_edit=False,
             **kw):

        notebook_tab = kw.get('notebook_tab') or 0
        params = self._read_form(context,
                                 count,
                                 domain,
                                 filter_domain,
                                 id,
                                 ids,
                                 kw,
                                 limit,
                                 model,
                                 offset,
                                 search_data,
                                 search_domain,
                                 source,
                                 view_ids,
                                 view_mode,
                                 view_type,
                                 notebook_tab,
                                 o2m_edit=o2m_edit,
                                 editable=True)

        if not params.ids:
            params.count = 0

        # On New O2M
        if params.source:
            current = TinyDict()
            current.id = False
            params[params.source] = current

        return self.create(params)

    @expose()
    def view(self,
             model,
             id,
             ids=None,
             view_ids=None,
             view_mode=['form', 'tree'],
             view_type='form',
             source=None,
             domain=[],
             context={},
             offset=0,
             limit=50,
             count=0,
             search_domain=None,
             search_data=None,
             filter_domain=None,
             **kw):

        notebook_tab = kw.get('notebook_tab') or 0
        params = self._read_form(context, count, domain, filter_domain, id,
                                 ids, kw, limit, model, offset, search_data,
                                 search_domain, source, view_ids, view_mode,
                                 view_type, notebook_tab)

        if not params.ids:
            params.count = 1

        return self.create(params)

    @expose()
    def cancel(self, **kw):
        params, data = TinyDict.split(kw)

        if params.button:
            res = self.button_action(params)
            if res:
                return res
            raise redirect('/')

        if not params.id and params.ids:
            params.id = params.ids[0]

        if params.id and params.editable:
            raise redirect(self.path + "/view",
                           model=params.model,
                           id=params.id,
                           ids=ustr(params.ids),
                           view_ids=ustr(params.view_ids),
                           view_mode=ustr(params.view_mode),
                           domain=ustr(params.domain),
                           context=ustr(params.context),
                           offset=params.offset,
                           limit=params.limit,
                           count=params.count,
                           search_domain=ustr(params.search_domain),
                           search_data=ustr(params.search_data),
                           filter_domain=ustr(params.filter_domain))

        params.view_type = 'tree'
        return self.create(params)

    @expose(methods=('POST', ))
    @validate(form=get_validation_schema)
    @error_handler(default_error_handler)
    @exception_handler(default_exception_handler)
    def save(self, terp_save_only=False, **kw):
        """Controller method to save/button actions...

        @param tg_errors: TG special arg, used durring validation
        @param kw: keyword arguments

        @return: form view
        """
        params, data = TinyDict.split(kw)
        # remember the current page (tab) of notebooks
        cherrypy.session['remember_notebooks'] = True

        Model = rpc.RPCProxy(params.model)
        # bypass save, for button action in non-editable view
        if params.editable:
            if not params.id:

                if params.default_o2m:
                    data.update(params.default_o2m)
                ctx = dict((params.context or {}), **rpc.session.context)
                params.id = int(Model.create(data, ctx))
                params.ids = (params.ids or []) + [params.id]
                params.count += 1
            else:
                original_data = Model.read(params.id, data.keys())
                modified = {}

                if original_data and isinstance(original_data, dict):
                    for field, original_value in original_data.iteritems():
                        if isinstance(original_value, tuple):
                            original_data[field] = original_value[0]
                        if field in data and data[field] != original_data[
                                field]:
                            #When field is many2many at that time following code will be applied
                            if isinstance(data[field], list) and isinstance(
                                    data[field][0][2], list):
                                if sorted(data[field][0][2]) != sorted(
                                        original_data[field]):
                                    modified[field] = data[field]
                            else:
                                modified[field] = data[field]

                    ctx = utils.context_with_concurrency_info(
                        params.context, params.concurrency_info)
                    Model.write([params.id], modified, ctx)
                else:
                    ctx = utils.context_with_concurrency_info(
                        params.context, params.concurrency_info)
                    Model.write([params.id], data, ctx)

            tw.ConcurrencyInfo.update(
                params.model, Model.read([params.id], ['__last_update'], ctx))

        cherrypy.request.params = params

        button = params.button

        # perform button action
        if params.button:
            res = self.button_action(params)
            if res:
                return res

        current = params.chain_get(params.source or '')
        if current:
            current.id = None
        elif not button:
            params.editable = False

        if terp_save_only:
            return dict(params=params, data=data)

        def get_params(p, f):

            pp = p.chain_get(f)
            px = rpc.RPCProxy(p.model)

            _ids = pp.ids
            _all = px.read([p.id], [f])[0][f]
            _new = [i for i in _all if i not in _ids]

            pp.ids = _all
            if _new:
                pp.id = _new[0]

            return pp

        if params.source and len(params.source.split("/")) > 1:

            path = params.source.split("/")
            p = params
            for f in path:
                p = get_params(p, f)

            return self.create(params)

        args = {
            'model': params.model,
            'id': params.id,
            'ids': ustr(params.ids),
            'view_ids': ustr(params.view_ids),
            'view_mode': ustr(params.view_mode),
            'domain': ustr(params.domain),
            'context': ustr(params.context),
            'offset': params.offset,
            'limit': params.limit,
            'count': params.count,
            'search_domain': ustr(params.search_domain),
            'search_data': ustr(params.search_data),
            'filter_domain': ustr(params.filter_domain),
            'notebook_tab': params.notebook_tab
        }
        if params.o2m_edit:
            # hack to avoid creating new record line when editing o2m inline:
            # by default one2many.mako is going to fetch a new line (.create)
            # on /edit
            args['o2m_edit'] = "1"

        if params.editable or params.source or params.return_edit:
            raise redirect(self.path + '/edit', source=params.source, **args)
        raise redirect(self.path + '/view', **args)

    def button_action_cancel(self, name, params):
        if name:
            params.button.btype = "object"
            params.id = False
            res = self.button_action(params)
            if res:
                return res

        import actions
        return actions.close_popup(reload=False)

    def button_action_save(self, _, params):
        params.id = False
        params.button = None

    def button_action_workflow(self, name, params):
        model, id, _, _ = self._get_button_infos(params)
        res = rpc.session.execute('object', 'exec_workflow', model, name, id)
        if isinstance(res, dict):
            import actions
            return actions.execute(res, ids=[id])
        params.button = None

    def button_action_object(self, name, params):
        model, id, ids, ctx = self._get_button_infos(params)

        res = rpc.session.execute('object', 'execute', model, name, ids, ctx)
        # after installation of modules (esp. initial) we may
        # need values from the global context for some contexts & domains (e.g.
        # leads) => installer wizards are generally postfixed by '.installer'
        # so use this characteristic to setup context reloads
        if model.endswith('.installer'):
            rpc.session.context_reload()
        if isinstance(res, dict):
            import actions
            return actions.execute(res, ids=[id], context=ctx)
        params.button = None

    def button_action_action(self, name, params):
        model, id, ids, ctx = self._get_button_infos(params)
        import actions

        action_id = int(name)
        action_type = actions.get_action_type(action_id)

        if action_type == 'ir.actions.wizard':
            cherrypy.session['wizard_parent_form'] = self.path
            cherrypy.session[
                'wizard_parent_params'] = params.parent_params or params

        res = actions.execute_by_id(action_id,
                                    type=action_type,
                                    model=model,
                                    id=id,
                                    ids=ids,
                                    context=ctx or {})
        if res:
            return res
        params.button = None

    BUTTON_ACTIONS_BY_BTYPE = {
        'action': button_action_action,
        'cancel': button_action_cancel,
        'object': button_action_object,
        'save': button_action_save,
        'workflow': button_action_workflow,
    }

    def _get_button_infos(self, params):
        model = params.button.model
        id = params.button.id or params.id
        id = (id or False) and (id)
        ids = (id or []) and [id]
        ctx = dict((params.context or {}), **rpc.session.context)
        ctx.update(params.button.context or {})
        return model, id, ids, ctx

    def button_action(self, params):
        button_name = openobject.ustr(params.button.name)
        button_name = button_name.rsplit('/', 1)[-1]

        btype = params.button.btype
        try:
            return self.BUTTON_ACTIONS_BY_BTYPE[btype](self, button_name,
                                                       params)
        except KeyError:
            raise common.warning(_('Invalid button type "%s"') % btype)

    @expose()
    def duplicate(self, **kw):
        params, data = TinyDict.split(kw)

        id = params.id
        ctx = params.context
        model = params.model

        proxy = rpc.RPCProxy(model)
        new_id = proxy.copy(id, {}, ctx)

        if new_id:
            params.id = new_id
            params.ids += [int(new_id)]
            params.count += 1

        args = {
            'model': params.model,
            'id': params.id,
            'ids': ustr(params.ids),
            'view_ids': ustr(params.view_ids),
            'view_mode': ustr(params.view_mode),
            'domain': ustr(params.domain),
            'context': ustr(params.context),
            'offset': params.offset,
            'limit': params.limit,
            'count': params.count,
            'search_domain': ustr(params.search_domain),
            'filter_domain': ustr(params.filter_domain)
        }

        if new_id:
            raise redirect(self.path + '/edit', **args)

        raise redirect(self.path + '/view', **args)

    @expose()
    def delete(self, **kw):
        params, data = TinyDict.split(kw)

        current = params.chain_get(params.source or '') or params
        proxy = rpc.RPCProxy(current.model)

        idx = -1
        if current.id:
            ctx = utils.context_with_concurrency_info(current.context,
                                                      params.concurrency_info)
            res = proxy.unlink([current.id], ctx)
            if current.ids:
                idx = current.ids.index(current.id)
                if idx >= 0:
                    current.ids.remove(current.id)
            params.count -= 1
            if not len(current.ids) and params.count > 0:
                params.offset = params.offset - params.limit
                current.ids = proxy.search([], params.offset, params.limit, 0,
                                           ctx)
                idx = -1
            if idx == len(current.ids):
                idx = -1
        current.id = (current.ids or None) and current.ids[idx]
        self.reset_notebooks()

        args = {
            'model': params.model,
            'id': params.id,
            'ids': ustr(params.ids),
            'view_ids': ustr(params.view_ids),
            'view_mode': ustr(params.view_mode),
            'domain': ustr(params.domain),
            'context': ustr(params.context),
            'offset': params.offset,
            'limit': params.limit,
            'count': params.count,
            'search_domain': ustr(params.search_domain),
            'filter_domain': ustr(params.filter_domain)
        }

        if not params.id:
            raise redirect(self.path + '/edit', **args)

        raise redirect(self.path + '/view', **args)

    @expose(content_type='application/octet-stream')
    def save_binary_data(self, _fname='file.dat', **kw):
        params, data = TinyDict.split(kw)

        cherrypy.response.headers[
            'Content-Disposition'] = 'attachment; filename="%s"' % _fname

        if params.datas:
            form = params.datas['form']
            res = form.get(params.field)
            return base64.decodestring(res)

        elif params.id:
            proxy = rpc.RPCProxy(params.model)
            res = proxy.read([params.id], [params.field], rpc.session.context)
            return base64.decodestring(res[0][params.field])
        else:
            return base64.decodestring(data[params.field])

    @expose()
    def clear_binary_data(self, **kw):
        params, data = TinyDict.split(kw)

        proxy = rpc.RPCProxy(params.model)
        ctx = utils.context_with_concurrency_info(params.context,
                                                  params.concurrency_info)

        if params.fname:
            proxy.write([params.id], {
                params.field: False,
                params.fname: False
            }, ctx)
        else:
            proxy.write([params.id], {params.field: False}, ctx)

        args = {
            'model': params.model,
            'id': params.id,
            'ids': ustr(params.ids),
            'view_ids': ustr(params.view_ids),
            'view_mode': ustr(params.view_mode),
            'domain': ustr(params.domain),
            'context': ustr(params.context),
            'offset': params.offset,
            'limit': params.limit,
            'count': params.count,
            'search_domain': ustr(params.search_domain),
            'filter_domain': ustr(params.filter_domain)
        }

        raise redirect(self.path + '/edit', **args)

    @expose(content_type='image/png')
    def binary_image_get_image(self, **kw):
        model = kw.get('model')
        field = kw.get('field')
        id = kw.get('id')
        proxy = rpc.RPCProxy(model)
        if id == 'None':
            # FIXME: doesnt honor the context
            res = proxy.default_get([field]).get(field, '')
        else:
            res = proxy.read([int(id)], [field])[0].get(field)
        if res:
            return base64.decodestring(res)
        else:
            return open(
                openobject.paths.addons('openerp', 'static', 'images',
                                        'placeholder.png'), 'rb').read()

    @expose("json")
    def binary_image_delete(self, **kw):
        saved = kw.get('saved') or None
        model = kw.get('model')
        id = kw.get('id')
        if id:
            id = int(id)
        field = kw.get('field')
        if id:
            proxy = rpc.RPCProxy(model)
            proxy.write([id], {field: False})
        return {}

    @expose()
    def b64(self, **kw):
        #idea from http://dean.edwards.name/weblog/2005/06/base64-ie/
        try:
            qs = cherrypy.request.query_string
            content_type, data = qs.split(';')
            data_type, data = data.split(',')
            assert (data_type == 'base64')
            cherrypy.response.headers['Content-Type'] = content_type
            return base64.decodestring(data)
        except:
            raise cherrypy.HTTPError(400)  # Bad request

    @expose()
    @validate(form=get_validation_schema)
    @error_handler(default_error_handler)
    @exception_handler(default_exception_handler)
    def filter(self, **kw):
        params, data = TinyDict.split(kw)
        if params.get('_terp_save_current_id'):
            ctx = dict((params.context or {}), **rpc.session.context)
            if params.id:
                rpc.RPCProxy(params.model).write([params.id], data, ctx)
            else:
                id = rpc.RPCProxy(params.model).create(data, ctx)
                params.ids.append(id)
                params.count += 1

        l = params.limit or 50
        o = params.offset or 0
        c = params.count or 0

        id = params.id or False
        ids = params.ids or []
        filter_action = params.filter_action

        if ids and filter_action == 'FIRST':
            o = 0
            id = ids[0]

        if ids and filter_action == 'LAST':
            o = c - c % l
            id = ids[-1]

        if ids and filter_action == 'PREV':
            if id == ids[0]:
                o -= l
            elif id in ids:
                id = ids[ids.index(id) - 1]

        if ids and filter_action == 'NEXT':
            if id == ids[-1]:
                o += l
            elif id in ids:
                id = ids[ids.index(id) + 1]
            elif id is False:
                o = 0
                id = ids[0]

        if filter_action:
            # remember the current page (tab) of notebooks
            cherrypy.session['remember_notebooks'] = True

        if params.offset != o:

            domain = params.domain
            if params.search_domain is not None:
                domain = params.search_domain
                data = params.search_data

            ctx = params.context or {}
            ctx.update(rpc.session.context.copy())
            res = search(params.model,
                         o,
                         l,
                         domain=domain,
                         context=ctx,
                         data=data)

            o = res['offset']
            l = res['limit']
            if not c: c = res['count']

            params.search_domain = res['search_domain']
            params.search_data = res['search_data']

            ids = res['ids']
            id = False

            if ids and filter_action in ('FIRST', 'NEXT'):
                id = ids[0]

            if ids and filter_action in ('LAST', 'PREV'):
                id = ids[-1]

        params.id = id
        params.ids = ids
        params.offset = o
        params.limit = l
        params.count = c

        return self.create(params)

    @expose()
    def find(self, **kw):
        kw['_terp_offset'] = None
        kw['_terp_limit'] = None

        kw['_terp_search_domain'] = None
        kw['_terp_search_data'] = None
        kw['_terp_filter_action'] = 'FIND'

        return self.filter(**kw)

    @expose()
    def first(self, **kw):
        kw['_terp_filter_action'] = 'FIRST'
        return self.filter(**kw)

    @expose()
    def last(self, **kw):
        kw['_terp_filter_action'] = 'LAST'
        return self.filter(**kw)

    @expose()
    def previous(self, **kw):
        if '_terp_source' in kw:
            return self.previous_o2m(**kw)

        kw['_terp_filter_action'] = 'PREV'
        return self.filter(**kw)

    @expose()
    def next(self, **kw):
        if '_terp_source' in kw:
            return self.next_o2m(**kw)

        kw['_terp_filter_action'] = 'NEXT'
        return self.filter(**kw)

    @expose()
    @validate(form=get_validation_schema)
    @error_handler(default_error_handler)
    @exception_handler(default_exception_handler)
    def previous_o2m(self, **kw):
        params, data = TinyDict.split(kw)

        if params.get('_terp_save_current_id'):
            ctx = dict((params.context or {}), **rpc.session.context)
            if params.id:
                rpc.RPCProxy(params.model).write([params.id], data, ctx)
            else:
                id = rpc.RPCProxy(params.model).create(data, ctx)
                params.ids.append(id)
                params.count += 1

        current = params.chain_get(params.source or '') or params
        idx = -1

        if current.id:
            # save current record
            if params.editable:
                self.save(terp_save_only=True, **kw)

            idx = current.ids.index(current.id)
            idx = idx - 1

            if idx == len(current.ids):
                idx = len(current.ids) - 1

        if current.ids:
            current.id = current.ids[idx]

        return self.create(params)

    @expose()
    def next_o2m(self, **kw):
        params, data = TinyDict.split(kw)
        c = params.count or 0
        current = params.chain_get(params.source or '') or params

        idx = 0
        if current.id:

            # save current record
            if params.editable:
                self.save(terp_save_only=True, **kw)

            idx = current.ids.index(current.id)
            idx = idx + 1

            if idx == len(current.ids):
                idx = 0

        if current.ids:
            current.id = current.ids[idx]

        return self.create(params)

    @expose()
    @validate(form=get_validation_schema)
    def switch(self, **kw):
        params, data = TinyDict.split(kw)
        if params.get('_terp_save_current_id'):
            ctx = dict((params.context or {}), **rpc.session.context)
            if params.id:
                rpc.RPCProxy(params.model).write([params.id], data, ctx)
            else:
                id = rpc.RPCProxy(params.model).create(data, ctx)
                params.ids.append(id)
                params.count += 1
        # switch the view
        params.view_type = params.source_view_type
        return self.create(params)

    def do_action(self, name, adds={}, datas={}):
        params, data = TinyDict.split(datas)

        model = params.model

        id = params.id or False
        ids = params.selection or params.ids or []

        if params.view_type == 'form':
            #TODO: save current record
            ids = (id or []) and [id]

        if id and not ids:
            ids = [id]

        if len(ids):
            import actions
            return actions.execute_by_keyword(name,
                                              adds=adds,
                                              model=model,
                                              id=id,
                                              ids=ids,
                                              report_type='pdf')
        else:
            raise common.message(_("No record selected"))

    @expose()
    def report(self, **kw):
        return self.do_action('client_print_multi',
                              adds={
                                  'Print Screen': {
                                      'report_name': 'printscreen.list',
                                      'name': _('Print Screen'),
                                      'type': 'ir.actions.report.xml'
                                  }
                              },
                              datas=kw)

    @expose()
    def action(self, **kw):
        params, data = TinyDict.split(kw)
        context_menu = kw.get('context_menu') or False

        id = params.id or False
        ids = params.selection or []

        if not ids and id:
            ids = [id]

        if not id and ids:
            id = ids[0]

        domain = params.domain or []
        context = params.context or {}
        action = {}

        if data.get('datas'):
            action = eval(data.get('datas'))
        type = action.get('type')
        act_id = params.action

        if not act_id:
            return self.do_action('client_action_multi', datas=kw)

        if type is None:
            action_type = rpc.RPCProxy('ir.actions.actions').read(
                act_id, ['type'], context)['type']
            tmp_ctx = dict(context)
            if action_type == 'ir.actions.report.xml':
                # avoid reading large binary values that we won't even care about
                tmp_ctx['bin_size'] = True
            action = rpc.session.execute('object', 'execute', action_type,
                                         'read', act_id, False, tmp_ctx)

        if domain:
            if isinstance(domain, basestring):
                domain = eval(domain)
            domain.extend(expr_eval(action.get('domain', '[]'), context))
            action['domain'] = ustr(domain)

        action['form_context'] = context or {}
        import actions
        return actions.execute(action,
                               model=params.model,
                               id=id,
                               ids=ids,
                               report_type='pdf',
                               context_menu=context_menu)

    @expose()
    def dashlet(self, **kw):
        params, data = TinyDict.split(kw)
        current = params.chain_get(str(params.source) or '') or params

        return self.create(current)

    @expose('json')
    def on_change(self, **kw):

        data = kw.copy()

        callback = data.pop('_terp_callback')
        caller = data.pop('_terp_caller')
        model = data.pop('_terp_model')
        context = data.pop('_terp_context')

        change_default = False
        if '_terp_change_default' in data:
            change_default = data.pop('_terp_change_default')

        try:
            context = eval(context)  # convert to python dict
        except:
            context = {}

        match = re.match('^(.*?)\((.*)\)$', callback)

        if not match:
            raise common.error(_('Application Error'),
                               _('Wrong on_change trigger: %s') % callback)

        for k, v in data.items():
            try:
                data[k] = eval(v)
            except:
                pass

        result = {}

        prefix = ''
        if '/' in caller:
            prefix = caller.rsplit('/', 1)[0]

        ctx = TinyForm(**kw).to_python(safe=True)
        pctx = ctx

        if prefix:
            ctx = ctx.chain_get(prefix)

            if '/' in prefix:
                pprefix = prefix.rsplit('/', 1)[0]
                pctx = pctx.chain_get(pprefix)

        ctx2 = dict(rpc.session.context, **context or {})

        ctx['parent'] = pctx
        ctx['context'] = ctx2

        func_name = match.group(1)
        arg_names = [n.strip() for n in match.group(2).split(',')]

        args = [utils.expr_eval(arg, ctx) for arg in arg_names]
        # TODO: If the eval fails in expr_eval (because `arg` does not exist in `ctx`), it returns `{}`
        # This is a value we don't want, but not sure where that behavior
        # comes from/is used so in order not to risk breakage throughout
        # patch it here
        args = [(False if arg == {} else arg) for arg in args]

        proxy = rpc.RPCProxy(model)

        ids = ctx.id and [ctx.id] or []

        try:
            response = getattr(proxy, func_name)(ids, *args)
        except Exception, e:
            return dict(error=_ep.render())

        if response is False:  # response is False when creating new record for inherited view.
            response = {}

        if 'value' not in response:
            response['value'] = {}

        result.update(response)

        # apply validators (transform values from python)
        values = result['value']
        values2 = {}
        for k, v in values.items():
            key = ((prefix or '') and prefix + '/') + k

            kind = data.get(key, {}).get('type', '')

            if key in data and key != 'id':
                values2[k] = data[key]
                values2[k]['value'] = v
            else:
                values2[k] = {'value': v}

            if kind == 'float':
                field = proxy.fields_get([k], ctx2)
                digit = field[k].get('digits')
                if digit: digit = digit[1]
                values2[k]['digit'] = digit or 2

        values = TinyForm(**values2).from_python().make_plain()

        # get name of m2o and reference fields
        for k, v in values2.items():
            kind = v.get('type')
            relation = v.get('relation')

            if relation and kind in ('many2one',
                                     'reference') and values.get(k):
                values[k] = [
                    values[k],
                    rpc.name_get(relation, values[k], context)
                ]

        result['value'] = values

        # convert domains in string to prevent them being converted in JSON
        if 'domain' in result:
            for k in result['domain']:
                result['domain'][k] = ustr(result['domain'][k])

        if change_default:
            value = data.get('_terp_value')
            proxy = rpc.RPCProxy('ir.values')
            values = proxy.get('default', '%s=%s' % (caller, value),
                               [(model, False)], False, context)
            for index, fname, value in values:
                if fname not in result['value']:
                    result['value'][fname] = value
        return result