1.對重載函數name_get的理解html
第一,此函數位於Model基類中,返回值是一個list列表,列表中的每一個值是如(key,value)形式的鍵值對,此處爲(id,name).前端
第二,在本身的Model類中若是重寫此函數,須要遵循第一條返回值的格式。node
第三,這個函數什麼時候調用呢。其一,用戶/開發人員明確調用類的id並在界面上顯示的時候,此時會使用該方法。其二,框架自身調用,好比用戶點擊tree視圖列表中的數據切換到form視圖的時候,Edit/Create按鈕上面的部分會顯示諸如 "action_name/當前id對應的name".app
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/ide
2.重載函數fileds_view_get的理解函數
第一,此函數仍舊位於Model基類中,從表ir_ui_view表或ir_model_data表返回值是一個類對象,大概包含了name,model,arch,type,view_id,fileds等相關值。post
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視圖中,根據用戶記錄顯示對應的偏好菜單。fetch
訂單Form視圖,自定義部分,下面的紅色部分:ui
<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中插入一條數據。