1.对重载函数name_get的理解
第一,此函数位于Model基类中,返回值是一个list列表,列表中的每个值是如(key,value)形式的键值对,此处为(id,name).
第二,在自己的Model类中如果重写此函数,需要遵循第一条返回值的格式。
第三,这个函数何时调用呢。其一,用户/开发人员明确调用类的id并在界面上显示的时候,此时会使用该方法。其二,框架自身调用,比如用户点击tree视图列表中的数据切换到form视图的时候,Edit/Create按钮上面的部分会显示诸如 "action_name/当前id对应的name".
def name_get(self, cr, uid, ids, context=None): if not ids: return [] res = [] for elmt in self.browse(cr, uid, ids, context=context): name = _("Lunch Order") name = name + ' ' + str(elmt.id) res.append((elmt.id, name)) return res
订单中我们的功能很简单,就是建立(id,“Lunch Order” + id )的键值列表。具体的运行效果如下图:
其他效果:http://odootechnical.com/overriding-name_get-method-in-odoo-8/
2.重载函数fileds_view_get的理解
第一,此函数仍旧位于Model基类中,从表ir_ui_view表或ir_model_data表返回值是一个类对象,大概包含了name,model,arch,type,view_id,fileds等相关值。
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): """ fields_view_get([view_id | view_type='form']) Get the detailed composition of the requested view like fields, model, view architecture :param view_id: id of the view or None :param view_type: type of the view to return if view_id is None ('form', 'tree', ...) :param toolbar: true to include contextual actions :param submenu: deprecated :return: dictionary describing the composition of the requested view (including inherited views and extensions) :raise AttributeError: * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace' * if some tag other than 'position' is found in parent view :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure """ if context is None: context = {} View = self.pool['ir.ui.view'] result = { 'model': self._name, 'field_parent': False, } # try to find a view_id if none provided if not view_id: # <view_type>_view_ref in context can be used to overrride the default view view_ref_key = view_type + '_view_ref' view_ref = context.get(view_ref_key) if view_ref: if '.' in view_ref: module, view_ref = view_ref.split('.', 1) cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref)) view_ref_res = cr.fetchone() if view_ref_res: view_id = view_ref_res[0] else: _logger.warning('%r requires a fully-qualified external id (got: %r for model %s). ' 'Please use the complete `module.view_id` form instead.', view_ref_key, view_ref, self._name) if not view_id: # otherwise try to find the lowest priority matching ir.ui.view view_id = View.default_view(cr, uid, self._name, view_type, context=context) # context for post-processing might be overriden ctx = context if view_id: # read the view with inherited views applied root_view = View.read_combined(cr, uid, view_id, fields=['id', 'name', 'field_parent', 'type', 'model', 'arch'], context=context) result['arch'] = root_view['arch'] result['name'] = root_view['name'] result['type'] = root_view['type'] result['view_id'] = root_view['id'] result['field_parent'] = root_view['field_parent'] # override context from postprocessing if root_view.get('model') != self._name: ctx = dict(context, base_model_name=root_view.get('model')) else: # fallback on default views methods if no ir.ui.view could be found try: get_func = getattr(self, '_get_default_%s_view' % view_type) arch_etree = get_func(cr, uid, context) result['arch'] = etree.tostring(arch_etree, encoding='utf-8') result['type'] = view_type result['name'] = 'default' except AttributeError: raise except_orm(_('Invalid Architecture!'), _("No default view of type '%s' could be found !") % view_type) # Apply post processing, groups and modifiers etc... xarch, xfields = View.postprocess_and_fields(cr, uid, self._name, etree.fromstring(result['arch']), view_id, context=ctx) result['arch'] = xarch result['fields'] = xfields # Add related action information if aksed if toolbar: toclean = ('report_sxw_content', 'report_rml_content', 'report_sxw', 'report_rml', 'report_sxw_content_data', 'report_rml_content_data') def clean(x): x = x[2] for key in toclean: x.pop(key, None) return x ir_values_obj = self.pool.get('ir.values') resprint = ir_values_obj.get(cr, uid, 'action', 'client_print_multi', [(self._name, False)], False, context) resaction = ir_values_obj.get(cr, uid, 'action', 'client_action_multi', [(self._name, False)], False, context) resrelate = ir_values_obj.get(cr, uid, 'action', 'client_action_relate', [(self._name, False)], False, context) resaction = [clean(action) for action in resaction if view_type == 'tree' or not action[2].get('multi')] resprint = [clean(print_) for print_ in resprint if view_type == 'tree' or not print_[2].get('multi')] #When multi="True" set it will display only in More of the list view resrelate = [clean(action) for action in resrelate if (action[2].get('multi') and view_type == 'tree') or (not action[2].get('multi') and view_type == 'form')] for x in itertools.chain(resprint, resaction, resrelate): x['string'] = x['name'] result['toolbar'] = { 'print': resprint, 'action': resaction, 'relate': resrelate } return result
第二,在自己的Model类重写此函数的时候,我们可以根据自己的需要做一些UI上的处理。例如我们的订单Form视图中,根据用户记录显示对应的偏好菜单。
订单Form视图,自定义部分,下面的红色部分:
<record model="ir.ui.view" id="orders_form_view"> <field name="name">Lunch Order</field> <field name="model">lunch.order</field> <field name="arch" type="xml"> <form string='Orders Form' class="oe_lunch"> <header> <field name='state' widget='statusbar' statusbar_visible='new,confirmed'/> </header> <sheet> <group> <group> <field name='user_id' context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'lunch.group_lunch_user']}"/> </group> <group> <field name='date'/> </group> </group> <field name='alerts' attrs="{'invisible': ['|',('state','!=','new'),('alerts','=',False)]}" class="oe_inline oe_lunch_alert"/> <div name="preferences"> </div> <separator string='Select your order'/> <field name='order_line_ids' nolabel='1' on_change='onchange_price(order_line_ids)'> <tree string='List' editable='bottom'> <field name='product_id' on_change='onchange_price(product_id)'/> <field name='note' /> <field name='price' on_change='onchange_price(product_id)'/> <field name='supplier' invisible="1"/> <field name="state" invisible="1"/> </tree> </field> <group class='oe_subtotal_footer oe_right'> <field name='total'/> </group> <br/><br/> </sheet> </form> </field> </record>
我们重写此函数,根据历史订单添加用户偏好的菜单和对应的按钮事件。
def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False): """ Add preferences in the form view of order.line """ res = super(lunch_order,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu) line_ref = self.pool.get("lunch.order.line") if view_type == 'form': doc = etree.XML(res['arch']) pref_ids = line_ref.search(cr, uid, [('user_id', '=', uid)], order='id desc', context=context) xml_start = etree.Element("div") #If there are no preference (it's the first time for the user) if len(pref_ids)==0: #create Elements xml_no_pref_1 = etree.Element("div") xml_no_pref_1.set('class','oe_inline oe_lunch_intro') xml_no_pref_2 = etree.Element("h3") xml_no_pref_2.text = _("This is the first time you order a meal") xml_no_pref_3 = etree.Element("p") xml_no_pref_3.set('class','oe_grey') xml_no_pref_3.text = _("Select a product and put your order comments on the note.") xml_no_pref_4 = etree.Element("p") xml_no_pref_4.set('class','oe_grey') xml_no_pref_4.text = _("Your favorite meals will be created based on your last orders.") xml_no_pref_5 = etree.Element("p") xml_no_pref_5.set('class','oe_grey') xml_no_pref_5.text = _("Don't forget the alerts displayed in the reddish area") #structure Elements xml_start.append(xml_no_pref_1) xml_no_pref_1.append(xml_no_pref_2) xml_no_pref_1.append(xml_no_pref_3) xml_no_pref_1.append(xml_no_pref_4) xml_no_pref_1.append(xml_no_pref_5) #Else: the user already have preferences so we display them else: preferences = line_ref.browse(cr, uid, pref_ids, context=context) categories = {} #store the different categories of products in preference count = 0 for pref in preferences: #For each preference categories.setdefault(pref.product_id.category_id.name, {}) #if this product has already been added to the categories dictionnary if pref.product_id.id in categories[pref.product_id.category_id.name]: #we check if for the same product the note has already been added if pref.note not in categories[pref.product_id.category_id.name][pref.product_id.id]: #if it's not the case then we add this to preferences categories[pref.product_id.category_id.name][pref.product_id.id][pref.note] = pref #if this product is not in the dictionnay, we add it else: categories[pref.product_id.category_id.name][pref.product_id.id] = {} categories[pref.product_id.category_id.name][pref.product_id.id][pref.note] = pref currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id #For each preferences that we get, we will create the XML structure for key, value in categories.items(): xml_pref_1 = etree.Element("div") xml_pref_1.set('class', 'oe_lunch_30pc') xml_pref_2 = etree.Element("h2") xml_pref_2.text = key xml_pref_1.append(xml_pref_2) i = 0 value = value.values() #TODO: sorted_values is used for a quick and dirty hack in order to display the 5 last orders of each categories. #It would be better to fetch only the 5 items to display instead of fetching everything then sorting them in order to keep only the 5 last. #NB: The note could also be ignored + we could fetch the preferences on the most ordered products instead of the last ones... sorted_values = {} for val in value: for elmt in val.values(): sorted_values[elmt.id] = elmt for key, pref in sorted(sorted_values.iteritems(), key=lambda (k, v): (k, v), reverse=True): #We only show 5 preferences per category (or it will be too long) if i == 5: break i += 1 xml_pref_3 = etree.Element("div") xml_pref_3.set('class','oe_lunch_vignette') xml_pref_1.append(xml_pref_3) xml_pref_4 = etree.Element("span") xml_pref_4.set('class','oe_lunch_button') xml_pref_3.append(xml_pref_4) xml_pref_5 = etree.Element("button") xml_pref_5.set('name',"add_preference_"+str(pref.id)) xml_pref_5.set('class','oe_link oe_i oe_button_plus') xml_pref_5.set('type','object') xml_pref_5.set('string','+') xml_pref_4.append(xml_pref_5) xml_pref_6 = etree.Element("button") xml_pref_6.set('name',"add_preference_"+str(pref.id)) xml_pref_6.set('class','oe_link oe_button_add') xml_pref_6.set('type','object') xml_pref_6.set('string',_("Add")) xml_pref_4.append(xml_pref_6) xml_pref_7 = etree.Element("div") xml_pref_7.set('class','oe_group_text_button') xml_pref_3.append(xml_pref_7) xml_pref_8 = etree.Element("div") xml_pref_8.set('class','oe_lunch_text') xml_pref_8.text = escape(pref.product_id.name)+str(" ") xml_pref_7.append(xml_pref_8) price = pref.product_id.price or 0.0 cur = currency.name or '' xml_pref_9 = etree.Element("span") xml_pref_9.set('class','oe_tag') xml_pref_9.text = str(price)+str(" ")+cur xml_pref_8.append(xml_pref_9) xml_pref_10 = etree.Element("div") xml_pref_10.set('class','oe_grey') xml_pref_10.text = escape(pref.note or '') xml_pref_3.append(xml_pref_10) xml_start.append(xml_pref_1) first_node = doc.xpath("//div[@name='preferences']") if first_node and len(first_node)>0: first_node[0].append(xml_start) res['arch'] = etree.tostring(doc) return res
我们重写的函数,首先调用基类的函数,获取默认情况下的form视图数据,然后我们向<div name="prefernces"></div>节点中添加html节点和相关数据,这样我们就动态改变了Form视图的结果。这里在向节点添加数据的时候,对Button按钮设置了事件,如上面的红色部分设置name属性为add_prefernces_1,add_prefernces_2,add_prefernces_3 .....为后面的事件处理打下伏笔。
第三,何时调用的问题。根据以前研究的代码,这个函数是前端UI框架中在显示Form UI之前主动调用的。
3.__getattr__函数的理解
1.这个函数似乎也是基类中的,但没有在源代码中找到。
2.何时调用的问题?估计是用户点击前端UI按钮触发事件失败的时候调用此函数。
def __getattr__(self, attr): """ this method catch unexisting method call and if it starts with add_preference_'n' we execute the add_preference method with 'n' as parameter """ if attr.startswith('add_preference_'): pref_id = int(attr[15:]) def specific_function(cr, uid, ids, context=None): return self.add_preference(cr, uid, ids, pref_id, context=context) return specific_function return super(lunch_order, self).__getattr__(attr)
我们订单中的按钮要触发诸如add_prefernces_1类型的事件,可这样的函数并不存在于我们的Mode类中,此时就会调用__getattr__函数,如果属性值以add_prefernce_开始,那么就默认调用specific_function函数,而该函数直接调用add_prefernce函数,参数值add_prefernce_1中的1,也即第15位之后的数字。
def add_preference(self, cr, uid, ids, pref_id, context=None): """ create a new order line based on the preference selected (pref_id) """ assert len(ids) == 1 orderline_ref = self.pool.get('lunch.order.line') prod_ref = self.pool.get('lunch.product') order = self.browse(cr, uid, ids[0], context=context) pref = orderline_ref.browse(cr, uid, pref_id, context=context) new_order_line = { 'date': order.date, 'user_id': uid, 'product_id': pref.product_id.id, 'note': pref.note, 'order_id': order.id, 'price': pref.product_id.price, 'supplier': pref.product_id.supplier.id } return orderline_ref.create(cr, uid, new_order_line, context=context)
此函数的主要功能是根据pref_id获取的值向表lunch.order.line中插入一条数据。