(03)odoo模型/记录集/公用操作

时间:2021-10-10 09:01:23

-----------------
更新时间
11:17 2016-09-18 星期日
11:00 2016-03-13 星期日
09:10 2016-03-03 星期四
11:46 2016-02-25 星期四
10:06 2016-02-24 星期三
14:51 2016-02-23 星期二
18:07 2016-02-19 星期五
17:44 2016-02-17 星期三
-----------------
*模型
  模型是业务对象的呈现
 
* 系统已定义的模型查看
  设置->技术->数据结构->模型
  现在定义的有700多个

* 版本演变
    from openerp.osv import fields, osv
    ->
    from openerp import models, fields
   
    -------------
   
    osv.osv ---> orm.Model ---> models.Model

osv.TransientModel ---> orm.TransientModel ---> models.TransientModel
    ------------
    class MyModel(osv.osv):
        pass...
    -------------   
    class MyModel(osv.Model):
        pass...   
    ------------
    class MyModel(models.Model):
        pass...
    -------------

上面这些在后台8.0版本都可以运行,在后面的版本不敢确定了
    确切的查看
    openerp/models.py 文件
    openerp/osv/osv.py 这个文件内容纯是兼容性写法
        except_osv = except_orm
        osv = Model
        osv_memory = TransientModel
        osv_abstract = AbstractModel
        左边是早期写法,很多技术文档,包含里面的官方模块也还有很多老的api
        新的,尽量用新的api来写模块
    openerp/osv/orm.py
        把openerp/models.py综合进来了
   
    系统里大部分还是老的写法,不过自己尽量用新的写法,会简洁一些
   
    差别最大是搜索:
    v7 search() 得到是ids  要得到recordset, 还得用 browse() 或 read()
    V8 search() 得到是recordset
    -----------
     def _get_language(self, cr, uid, context):
        lang_model = self.pool.get('res.lang')
        lang_ids = lang_model.search(cr, uid, [('translatable', '=', True)], context=context)
        lang_data = lang_model.read(cr, uid, lang_ids, ['code', 'name'], context=context)
        return [(d['code'], d['name']) for d in lang_data]
    -----------
    def _get_language(self):
        lang_model = self.env.get('res.lang')
        或
        # lang_model = self.env['res.lang']       
        lang_data = lang_model.search_read([('translatable', '=', True)],['code', 'name'])
        return [(d['code'], d['name']) for d in lang_data]
    -----------
    @ v7 版
   
* 常用模型 models.model, models.TransientModel, models.AbstractModel
    model:是基本模型,继续了它,模型就具有官方的ORM功能了
    TransientModel:常用于向导模型,数据是存入数据库,但不是永久的
    AbstractModel:常用于混合继承

* 创建模型:
  from openerp import models, fields
  class Stage(models.Model):
    _name = 'todo.task.stage' 
    _order = 'sequence,name'
    _rec_name = 'name'
    _table = 'todo_task_stage'
   
    field1 = fields.Char()
    _columns = {
        'name': fields.char('State', size=64, required=True, translate=True),
        'sequence': fields.integer('Sequence', help="Used to order states."),
        'state_color': fields.selection(STATE_COLOR_SELECTION, 'State Color'),
        'team': fields.selection(STATE_SCOPE_TEAM, 'Scope Team'),
    }

_defaults = {
        'sequence': 1,
    }
   
    哪些关键词可以定义,可查看 /openerp/models.py 中的class BaseModel(object): 下面的定义
   
    # _name 模型的标识符,用于引用,全局唯一,一般格式 "ModuleName.ClassName"
    # _rec_name 覆盖默认的name字段,重新定义,一般会加其它合成方法生成名称,
    # _table 映射到数据库的表名,可自定义,默认是模型标识符把"."换成"_" 连启来   
    # _columns 设置字段名 字典{字段名->字段声明}
    # _defaults 设置字段的默认值
    # _auto 是否创建对象对应的table 缺省为True
    # _inherit 继承对象当不再定义 _name时,就是用父对象,若再定义_name 就生成了新的对象
    # _inherits 继承对象,且创建新的对象,不拥有父对象的字段,但可以操作父对象的字段
    # _constraints 对象约束
        def _check_date(self,cr,uid,ids)
            for rec in self.browse(cr,uid,ids):
                search_ids =self.search(cr,uid,[('date_from','<=', rec.date_to),('date_to','>=',rec.date_from),
                   ('employee_id','=', rec.employee_id.id),('id','<>',rec.id)])
                if search_ids:
                    return False
                return True
                _constraints=[_check_date,u'你在相同的时间段不允许创建多张请假条!',[u'起始日期',u'结束日期']]
               
    # _sql_constraints 数据库约束,最底层级别的约束
         (标识,条件,显示字符)
        _sql_constraints =[
                ('date_check',"CHECK(date_from <= date_to)",u"开始日期必须小于结束日期"),
                ('days_check',"CHECK(days>0)",u"请假天数必须大于0")
            ]
   
    # _log_access 是否自动在对应的数据表中增加 create_uid,create_date,write_uid,write_date 四个字段
                  可用于对象的方法(perm_read)读取,权限控制
                  缺省是True
                 
    # _order 定义search()和read()方法是排序,缺省是 id 升序
    # _parent_store
    # _parent_name
    # _parent_order
   
   
    得到模型
     self.env['todo.task.stage']  或 self.env.get('todo.task.stage')
    旧api 是 self.pool['todo.task.stage']或self.pool.get('todo.task.stage')
    
    Transient 和 Abstract
   
    查看系统已注册的模型
     技术->数据结构->模型   
    
* 三种常用继承 (在model中操作)
    _inherit 没重定义_name  这样数据库不会新建对象表,用被继承的表
    _inherit 重定义_name 这样数据库会创建新的对象表存储
    _inherits 是复合继承,有模型对象,有字段对象
   
    示例:
   
    class MyModelExtended(Model):
     _inherit = 'a.model'                       # direct heritage
     _inherit = ['a.model, 'a.other.model']     # direct heritage
     _inherits = {'a.model': 'field_name'}      # polymorphic heritage

* 基本字段类型:
    具有哪些字段类型可以查看 openerp/fields.py 中定义的字段类
    例子:
    name = fields.Char('Name',40,translate=True)
    desc = fields.Text('Description')
    state = fields.Selection( [('draft','New'), ('open','Started'),('done','Closed')],'State')
    docs = fields.Html('Documentation')
    sequence = fields.Integer('Sequence')
    perc_complete = fields.Float('% Complete',(3,2))
    date_effective = fields.Date('Effective Date')
    date_changed = fields.Datetime('Last Changed')
    fold = fields.Boolean('Folded')
    image = fields.Binary('Image')
   
    用方法设置默认值:
    a_field = fields.Char(default=compute_default_value)
    def compute_default_value(self):
        return self.get_value()
   
    注:参数self,现在python2.7.x 只是类中的方法,第一个参数就是self
        但在调用这个方法的时候不必为这个参数赋值
        例子说明:创建了一个类MyClass,实例化MyClass得到了MyObject这个对象,
           然后调用这个对象的方法MyObject.method(arg1,arg2) ,这个过程中,
           Python会自动转为Myclass.mehod(MyObject,arg1,arg2)

# Char 字符串
        size:字符串长度
        translate 字符串可翻译
       
   # Text 文本 ,没有长度限制
        translate 字符串可翻译
       
   # Selection 下接选择列表
        下接选择列表
        例子:
            aselection = fields.Selection([('a', 'A')])
            aselection = fields.Selection(selection=[('a', 'A')])
            aselection = fields.Selection(selection='a_function_name')
           
            class SomeModel(models.Model):
                _inherits = 'some.model'
                type = fields.Selection(selection_add=[('b', 'B'), ('c', 'C')])    #增加选项               
       
   # Html html文本
        translate 字符串可翻译
       
   # Integer 整数
   # Float   浮点数
        digits 设定 整数部分的长度,和小数部分的精度位 afloat = fields.Float(digits=(32, 32))
        精度也可以用方法去计算 afloat = fields.Float(digits_compute=fun())  fun是自定义方法,返回
        整数部分的长度,和小数部分的精度位 的元组
   # Date  日期
        context_tody 得到今天的日期
        today 得到系统的日期
        from_string 从字符串转转成日期对象
        to_string 从日期对象转字符串
        例子:
            >>> from openerp import fields

>>> adate = fields.Date()
            >>> fields.Date.today()
            '2014-06-15'
            >>> fields.Date.context_today(self)
            '2014-06-15'
            >>> fields.Date.context_today(self, timestamp=datetime.datetime.now())
            '2014-06-15'
            >>> fields.Date.from_string(fields.Date.today())
            datetime.datetime(2014, 6, 15, 19, 32, 17)
            >>> fields.Date.to_string(datetime.datetime.today())
            '2014-06-15'
        日期格式化 DATE_FORMAT='%Y-%m-%d'
        字段赋当前日期 fields.date.context_today, fields.date.context_today(self,cr,uid,context=context),
                       fields.date.today()   
        官方建议写法 fields.date.context_today
        字符串转日期 datetime.datetime.strptime(sale.date,DATE_FORMAT)
        日期转字符串 datetime.datetime.strftime(datetme.date.today(),DATE_FORMAT)
        python获取当前日期 datetime.date.today()
           
   # Datetime 时间
        context_timestamp 得到今天的时间
        now 得到系统的时间
        from_string 从字符串转转成日期对象
        to_string 从日期对象转字符串   
        例子:
            >>> fields.Datetime.context_timestamp(self, timestamp=datetime.datetime.now())
            datetime.datetime(2014, 6, 15, 21, 26, 1, 248354, tzinfo=<DstTzInfo 'Europe/Brussels' CEST+2:00:00 DST>)
            >>> fields.Datetime.now()
            '2014-06-15 19:26:13'
            >>> fields.Datetime.from_string(fields.Datetime.now())
            datetime.datetime(2014, 6, 15, 19, 32, 17)
            >>> fields.Datetime.to_string(datetime.datetime.now())
            '2014-06-15 19:26:13'   
        return { 'value': {'shop_accept_datetime': time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time())) }}
       
        时间格式化 DATETIME_FORMAT="%Y-%m-%d %H:%M:%S"
        包含毫秒 DATETIME_FORMAT="%Y-%m-%d %H:%M:%S.%f"
        字段赋当前时间 fields.datetime.now(), fields.datetime.context_timestamp(self,cr,uid,datetime.now(),context=context)
        官方建议写法 fields.datetime.now()
        python 获取当前时间 datetime.datetime.now()
        --------
        'datetime': fields.datetime('Historization Time')
        _defaults = {
        'datetime': fields.datetime.now, #赋当前时间
        }
       
       
   # Boolean 布尔值(true, false)   若default=True 复选框默认选上
   # Bninary 二进制
        储存文件采用 base64编码
       
   # function: 函数型,该类型的字段,字段值由函数计算而得,不存储在数据表中
     其定义格式为:
        fields.function(fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float',
           fnct_search=None, obj=None, method=False, store=True)
          
        @ fcnt 是函数或方法,用于计算字段值。如果method = true, 表示fcnt是对象的方法,其格式如下:
          def fnct(self, cr, uid, ids, field_name, args, context),否则,其格式如下:
          def fnct(cr, table, ids, field_name, args, context)。ids是系统传进来的当前
          存取的record id。field_name是本字段名,当一个函数用于多个函数字段类型时,
          本参数可区分字段。args是'arg=None'传进来的参数。 返回字典
        @ arg 是传给fcnt的实际参数
        @ method 为True表示本字段的函数是对象的一个方法,为False表示是全局函数,不是对象的方法。
          如果method=True,obj指定method的对象。
        @ fcnt_inv 是用于写本字段的函数或方法(没有提供则本字佰只读)。如果method = true, 其格式是:
          def fcnt_inv(self, cr, uid, ids, field_name, field_value, args, context),否则格式为:
          def fcnt_inv(cr, table, ids, field_name, field_value, args, context)
        @ fnct_inv_arg 传给写本字佰方法的实际参数
        @ type 是函数返回值的类型。
        @ fcnt_search 定义该字段的搜索行为(不提供不返回任何结果)。如果method = true, 其格式为:
          def fcnt_search(self, cr, uid, obj, field_name, args),否则格式为:def fcnt_search(cr, uid, obj, field_name, args)
        @ store 表示是否希望在数据库中存储本字段值,缺省值为False。true表示存入 若采用增强形式,则会重新计算字段再存入
          不过store还有一个增强形式,格式为 store={'object_name':(function_name,['field_name1','field_name2'],priority)} ,
          其含义是,如果对象'object_name'的字段['field_name1','field_name2']发生任何改变,系统将调用函数function_name,
          函数的返回结果将作为参数(arg)传送给本字段的主函数,即fnct。
         
   # dummy:虚拟型 fields.dummy(string='Pricelist', relation='product.pricelist', type='many2one')
         string:显示字符
         relation:关联对象
         type:关联到对象的类型,这里是many2one        
         
   # many2one: 多对一关系,格式为:fields.many2one(关联对象Name, 字段显示名, ... )。可选参数有:
        ondelete,可选值为"cascade"和"null",缺省值为"null",表示one端的record被删除后,many端的record是否级联删除。
   # one2many: 一对多关系,格式为:fields.one2many(关联对象Name, 关联字段, 字段显示名, ... ),例:
       'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts')。
   # many2many: 多对多关系。例如: 'category_id':fields.many2many('res.partner.category','res_partner_category_rel',
        'partner_id','category_id','Categories'), 表示以多对多关系关联到对象res.partner.category,
        关联表为'res_partner_category_rel',关联字段为 'partner_id'和'category_id'。当定义上述字段时,
        OpenERP会自动创建关联表为 'res_partner_category_rel',它含有关联字段'partner_id'和'category_id'     
   # reference: 引用型,
        格式为:fields.reference(字段名, selection, size, ... )。
        其中selection是:
        1)返回tuple列表的函数,或者 2)表征该字段引用哪个对象(or model)的tuples列表。
        reference字段在数据库表中的存储形式是(对象名,ID),如(product.product,3)
        3)表示引用对象product.product(数据表product_product)中id=3的数据。reference的例子:
        def _links_get(self, cr, uid):
            cr.execute('select object,name from res_request_link order by priority')
            return cr.fetchall()
            ...
            'ref':fields.reference('Document Ref 2', selection=_links_get, size=128),
            ...
        上例表示,字段ref可以引用哪些对象类型的resource,可引用的对象类型从下拉框选择。
        下拉框的选项由函数_links_get返回,是(object,name)对的列表,如[("product.product","Product"),
        ("account.invoice","Invoice"), ("stock.production.lot","Production Lot")] 。

# related: 关联字段,表示本字段引用关联表中的某字段。特别用于一对多,
        比如一个移库单包含多个库存移动单,要在一个移库单紧着罗列出关联的所有库存移动单
        这时采用 related 定义字段特别有用。
       
        格式为:fields.related(关系字段,引用字段,type, relation, string, ...),
        关系字段是本对象的某字段(通常是one2many or many2many),
        引用字段是通过关系字段关联的数据表的字段,
        type是引用字段的类型,若关联的字段是many2one 或 many2many 类型,那就要明确指定
        如果type是many2one or many2many, relation指明关联表。例子如下:
        'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'),
       
        'city':fields.related('address','city',type='char', string='City'),
        'country':fields.related('address','country_id',type='many2one', relation='res.country', string='Country'),
        这里,city引用address的city字段,country引用address的country对象。在address的关联对象res.partner.address中,
        country_id是many2one类型的字段,所以type='many2one', relation='res.country'。

# property: 属性字段,下面以具体例子解说property字段类型。
        'property_product_pricelist': fields.property('product.pricelist', type='many2one',
        relation='product.pricelist',string="Sale Pricelist", method=True, view_load=True, group_name="Pricelists Properties")
        这个例子表示,本对象通过字段'property_product_pricelist'多对一(type='many2one')
        关联到对象 product.pricelist(relation='product.pricelist')。和many2one字段类型不同的是,
        many2one字段会在本对象中创建数据表字段'property_product_pricelist',property字段类型不会
        创建数据表字段'property_product_pricelist'。property字段类型会从数据表ir.property中查找
        name='property_product_pricelist'(即字段定义中的'product.pricelist'加上前缀 property,
        并将"."替换成"_"作为name)且company_id和本对象相同的记录,从该记录的value字段(value字段
        类型为 reference)查得关联记录,如(product.pricelist,1),表示本对象的resource多对一关联
        到对象 product.pricelist的id=1的记录。也就是说,property字段类型通过ir.property间接多对一关联到别的对象。
        property字段类型基本上和many2one字段类型相同,但是有两种情况优于many2one字段。其一是,例如,
        当有多条记录通过 ir.property的name='property_product_pricelist'的记录关联到记录 (product.pricelist,1),
        此时,如果希望将所有关联关系都改成关联到记录(product.pricelist,2)。如果是 many2one类型,不写代码,
        很难完成此任务,是property字段的话,只要将ir.property中的value值 (product.pricelist,1)
        改成(product.pricelist,2),则所有关联关系都变了。修改ir.property的 value值可以在系统管理下的
        菜单Configuration --> Properties中修改。其二是,例如,同一业务伙伴,但希望A公司的用户进来看到
        的该业务伙伴价格表为pricelistA,B公司的用户进来看到的该业务伙伴价格表为pricelistB,
        则many2one类型达不到该效果。property类型通过ir.property中的记录关联时加上了 company_id的条件,
        因此可以使得不同公司的员工进来看到不同的关联记录。
        由于property类型通过ir.property关联,因此,每个property类型的字段都必须在ir.property中有一条关联记录。
        这可以在安装时导入该条记录,参考代码如下:
        <record model="ir.property" id="property_product_pricelist">
        <field name="name">property_product_pricelist</field>
        <field name="fields_id" search="[('model','=','res.partner'), ('name','=','property_product_pricelist')]"/>
        <field name="value" eval="'product.pricelist,'+str(list0)"/>
        </record>
       
* 字段使用精度   
        import openerp.addons.decimal_precision as dp 这个模块主要处理字段的精度
        #设置->技术->数据结构->小数精度 这里面定义各情况的小数精度
        ....
        'list_price': fields.float('Sale Price', digits_compute=dp.get_precision('Product Price'),
        销售价格,采用系统 Product Price的小数精度保留
       
* 字段集中写法
     _columns = {
        'name':fields.char( u'课程名',size=64,select=True),
        'date_start':fields.date(u'开始日期',select=True),
        'total_day':fields.float(u'总天数',digits=(16,1)),
        'teacher':fields.many2one('res.users',u'授课老师'),
        'students':fields.many2many('res.partner',string=u'学生'),
        'price':fields.float(u'价格',digits=(16,2)),
    }
    采用_columns 关键词来描述

* 普通字段属性
   在 openerp/fields.py 中 _slots 有定义
  
   # string  字段显示名label,任意字符串
   # help='str' 用于悬挂帮助信息提示
   # readonly=True 本字段是否只读,缺省值:False
   # required=True 本字段是否必须的,缺省值:False。
   # index = True 设置字段创建索引
   # select=True 相关联字段下拉列表
   # default 设置字段的默认值
     name = fields.Char(default="Unknown")
     user_id = fields.Many2one('res.users', default=lambda self:self.env.user)
   # states 定义特定state才生效的属性,格式为:{'name_of_the_state': list_of_attributes},
      其中list_of_attributes是形如[('name_of_attribute', value), ...]的tuples列表。
      例子(参见account.transfer): 'partner_id': fields.many2one('res.partner', 'Partner',
      states={'posted':[('readonly',True)]}),
   # groups 设置权限组访问可见,值为列表形式,元素是组的xml ids(也就是外部标识符)
   # copy=False 设置字段不能被复制,普通字段是True, 在 one2many是默认是False
   # oldname='field' 当一个字段在新版本重命名了,指定老的字段,老的字段的数据会自动拷贝到新的字段中
   # compute 给定一个方法名计算出该字段
   # inverse 给定一个方法名反转该字段 (为了在计算字段设置值)
   document = fields.Char(compute='_get_document', inverse='_set_document')

def _get_document(self):
        for record in self:
            with open(record.get_document_path) as f:
                record.document = f.read()
    def _set_document(self):
        for record in self:
            if not record.document: continue
            with open(record.get_document_path()) as f:
                f.write(record.document)
               
   # search  给定一个方法名搜索该字段 (用在计算字段)
   upper_name = field.Char(compute='_compute_upper', search='_search_upper')

def _search_upper(self, operator, value):
        if operator == 'like':
            operator = 'ilike'
        return [('name', operator, value)]
       
   # store 设定该字段是否存在数据表中,在用了compute时为False ,其它默认是True
   # compute_sudo 设定是否要超级用户来计算该字段,默认是False
    upper = fields.Char(compute='_compute_upper',
                                inverse='_inverse_upper',
                                search='_search_upper')

@api.depends('name')
            def _compute_upper(self):
                for rec in self:
                    rec.upper = rec.name.upper() if rec.name else False

def _inverse_upper(self):
                for rec in self:
                    rec.name = rec.upper.lower() if rec.upper else False

def _search_upper(self, operator, value):
                if operator == 'like':
                    operator = 'ilike'
                return [('name', operator, value)]
    --------           
    from openerp import api
    total = fields.Float(compute='_compute_total')

@api.depends('value', 'tax')
    def _compute_total(self):
        for record in self:
            record.total = record.value + record.value * record.tax   
           
   # related 序列的字段名,用于关联字段,默认不存数据表中,可以store=True来存,
     nickname = fields.Char(related='user_id.partner_id.name', store=True)
     这里看出前面定义了user_id字段,这里是接着关联到 user_id这个字段把所要的信息提取出来
    
   # company_dependent 定义该字段是不是公司依赖
   # change_default 别的字段的缺省值是否可依赖于本字段,缺省值为:False。
     例子(参见res.partner.address) 'zip': fields.char('Zip', change_default=True, size=24),
     这个例子中,可以根据zip的值设定其它字段的缺省值,例如,可以通过程序代码,
     如果zip为200000则city设为“上海”,如果zip为100000则city为“北京”。
   # domain: 域条件,缺省值:[]。在many2many和many2one类型中,字段值是关联表的id,
     域条件用于过滤关联表的record。例子: 'default_credit_account_id': fields.many2one('account.account',
     'Default Credit Account', domain="[('type','!=','view')]"),
     本例表示,本字段关联到对象('account.account')中的,type不是'view'的record。
   # context 上下文    
   # invisible: 本字段是否可见,即是否在界面上显示本字段,缺省值True。
   # selection: 只用于reference字段类型,参见前文reference的说明。  
   # size  只用于Char 设置可最大接收字符数
   # translate=true 用于Text Char Html 本字段值(不是字段的显示名)是否可翻译,缺省值:False。
   # ondelete 用于关联字段,定义删除方式
   # manual  
   # deprecated=True 当用这个字段,日志会记录警告,主要是版本升级一些过渡字段 
   # password ="True" 密码星号显示
   # nolabel ="1" 隐藏标签
   # attr属性 可以定义多条件这段只读显示
   # digits 格式化浮点数
       <field digits="(14,3)" name="volume" attrs="{readonly:[('type','=','service')]}"
   # default_focus 新开窗口光标位置
   # Widget 多种部件显示格式 widget="one2many_list"
            one2one_list, one2many_list, many2one_list, many2many,Url,Email,Image,float_time,reference
           
   不同的字段类型具有不同的属性,上面是公用的居多,基它的可以在
   openerp/fields.py  和 openerp/osv/fields.py 中找,主要看 __slots__的列表值

*保留字段名字
   # id 数据库自动生成做为主键
   # create_uid 创建记录时的用户
   # create_date 创建记录时的日期和时间
   # write_uid 修改记录时的最后用户
   # write_date 修改记录时最后日期和时间

*内置专用字段名
   # name  (Char)  记录的默认名,但可以用 _rec_name 去覆盖  
   # active (Boolean) 标识记录的有效性 常用于domain  domain是元组表达式
   # sequence (Integer) 用在列表视图记录排序
   # state (Selection) 显示记录的生命周期
   # parent_id, parent_left, parent_right  用于记录的上下级
  
* 部件字段类型(Widgets)
   # image(binary)
   # monetary(float)
   # duration (float)
   # relative(datetime)
   # contact(res.partner many2one)
  
* 字段条件约束
    _constraints=[(check_phone,u'手机号码和电话号码不能同时为空',['mobile_number','phone_number']),
       (check_estimated_ship_date,u'期望发货日期必须比当前日期要大',['estimated_ship_date'])]
    def check_phone(self, cr, uid, ids, context=None):
        for apply_delivery in self.browse(cr,uid,ids,context=context):
            if apply_delivery.mobile_number or apply_delivery.phone_number:
                return True
        return False
    def check_estimated_ship_date(self, cr, uid, ids, context=None):
        apply_delivery =self.browse(cr,uid,ids,context=context)
        estimated_ship=time.strptime(apply_delivery[0].estimated_ship_date,'%Y-%m-%d %H:%M:%S')
        if time.mktime(estimated_ship)
            return False
        return True  
      
    _sql_constraints = {
        ('internal_code_uniq', 'unique(internal_code)',
            'Internal Code mast be unique!')
    }
     internal_code 必须是唯一   
    
    新api
    from openerp.exceptions import ValidationError

@api.constrains('age')
    def _check_something(self): # self是一个记录集,要遍历,便也可用@api.one 来辅助
        for record in self:
            if record.age > 20:
                raise ValidationError("Your record is too old: %s" % record.age)

* 模型的关系
  例子:
  class TodoTask(models.Model):
    _inherit = 'todo.task'
    stage_id = fields.Many2one('todo.task.stage','Stage')
    tage_ids =fields.Many2many('todo.task.tag',string='Tages')

model 和 string 所有关系都接受属性,
   # 多对一 Many2one
      意思就是到对方的对象上选择一个用来关联本记录
       parent_id = fields.Many2one(string='Parent Tag', comodel_name='product.tag',
             select=True, ondelete='cascade')
            
         string    :显示标签名称
         comodel_name:引用的目标模型名
         select=True  字段加上索引
         domain
         context 是上下文
         ondelete 默认值为set null 表示关联记录删除 本字段设为空
                   restrict 关联对方记录删除,本关联字段不让其删除,要删除本关联
                             字段的记录,才能删除关联对方的记录
                    cascade 关联对方记录删除,对于本关联字段对应的记录全部跟着删除        
         auto_join=False 允许用sql 语句来关联
         delegate=False
        
        
        
   # 一对多 One2Many
     接受的属性 和 Many2one 一样
     可以很简单地从one得到many方的对应的所有记录
              class Stage(models.Model):
                 _name = 'todo.task.stage'
                 Stage class relation with Tasks:
                 tasks = fields.One2many(
                   'todo.task', # 关联对象
                   'stage_id', # 本对象提供给关联对象关联的字段,这里是主键
                                 这个字段只要是唯一就可以
                   'Tasks in this stage') 
                  
     parent_id = fields.Many2one(string='Parent Tag', comodel_name='product.tag',
           select=True, ondelete='cascade')
     child_ids = fields.One2many(string='Child Tags', comodel_name='product.tag',
         inverse_name='parent_id')   
   
     这里parent_id会存入数据表,child_ids就不会了   
     string    :显示标签名称
     comodel_name:引用的目标模型名
     inverse_name 返向关联的字段 对应Many2one
     domain:
     context:
     auto_join=False 允许用sql 语句来关联
     limit='1000' 定义读时出多少个记录
     copy=False
   
   # 正反关联定义:
     Many2one 和 one2many 是成对出现的
     Many2one 找关联对象
     One2many 提供给关联对象的关联字段   
     @ 正:
        class TodoTask(models.Model):
            _inherit = 'todo.task'
            stage_id = fields.Many2one('todo.task.stage','Stage')
     @ 反:
        class Stage(models.Model):
             _name = 'todo.task.stage'
              tasks = fields.One2many(
               'todo.task',
               'stage_id',
               'Tasks in this stage')

# 多对多 Many2many
     会转换为Many2one Many2one
     这个one,自然就是境加的中间关联表,一般表名后缀为 _rel
       @ class 为 TodoTask
             tag_ids = fields.Many2many(
                 'todo.task.tag', # related model
                 'todo_task_tag_rel', # relation table name
                 'task_id', # field for "this" record
                 'tag_id', # field for "other" record
                 string='Tasks')

也可以用长表单键表示
             tag_ids = fields.Many2many(
                 comodel_name='todo.task.tag', # related model
                 relation='todo_task_tag_rel', # relation table name
                 column1='task_id', # field for "this" record
                 column2='tag_id', # field for "other" record
                 string='Tasks')
            
      string:显示标签名称   
      comodel_name:引用的目标模型名
      relation:存多对多关系的关系表名
      column1;存目标模型关系字段在关系表中
      column1;存本模型关系字段在关系表中
      domain
      context 是上下文
      limit='1000' 定义读时出多少个记录
     
      对应对方关联对象也要加一条反向关联,这个相对简单
       @ class为 Tag
           task_ids = fields.Many2many(
                'todo.task',# related model
                string='Tasks' )

# 父子分层
     可以用Many2one 表示子到父, 用One2many 表示父到子

class Tags(models.Model):
                _name = 'todo.task.tag'
                _parent_store = True
                # _parent_name = 'parent_id'
                name = fields.Char('Name')
                parent_id = fields.Many2one(
                  'todo.task.tag', 'Parent Tag', ondelete='restrict')
                parent_left = fields.Integer('Parent Left', index=True)
                parent_right = fields.Integer('Parent Right', index=True)
     为了方便还会加一个字段
                child_ids = fields.One2many('todo.task.tag', 'parent_id', 'Child Tags')
   # One2Many 和 Many2many 的 eval 赋值
     <field name=”tag_ids”
        eval=”[(6,0,
        [ref(’vehicle_tag_leasing’),
        ref(’fleet.vehicle_tag_compact’),
        ref(’fleet.vehicle_tag_senior’)]
        )]” />
       
      (0,_ ,{’field’: value}) 这将创建一个新的记录并连接它
      (1,id,{’field’: value}): 这是更新一个已经连接了的记录的值
      (2,id,_) 这是删除或取消连接某个已经连接了的记录
      (3,id,_) 这是取消连接但不删除一个已经连接了的记录
      (4,id,_) 连接一个已经存在的记录
      (5,_,_) 取消连接但不删除所有已经连接了的记录
      (6,_,[ids]) 用给出的列表替换掉已经连接了的记录
      这里的下划线一般是0或False

# 引用字段动态关系 
       Refers to

class TodoTask(models.Model):
                 refers_to = fields.Reference(
                   [('res.user', 'User'), ('res.partner', 'Partner')],
                   'Refers to')

可用保用任何注册模型
    
     from openerp.addons.base.res import res_request
     def referencable_models(self):
         return res_request.referencable_models(
           self, self.env.cr, self.env.uid, context=self.env.context)
          
    class TodoTask(models.Model):
          refers_to = fields.Reference(
             referencable_models, 'Refers to')    
    这样就不会只局限在用户和合作伙伴两个模型了

* 字段设置
   不是所有定义都会存到数据表中
   首先_auto 要为true 缺省也是true
   #普通定义
    name = fields.Char('Tag Name', required=True, translate=True)
   #显示名称取代通过计算
    _rec_name='display_name'
    display_name = field.Char('Full Name' compute="_compute_display_name")
    #这个是不会存数据表中的
        @api.one
        @api.depends('name', 'parent_id.name')
        def _compute_display_name(self):
            """ Return the tags' display name, including their direct parent. """
            if self.parent_id:
                self.display_name = self.parent_id.display_name + ' / ' + self.name
            else:
                self.display_name = self.name 
   #               
          
* 字段操作
   # 计算
     class TodoTask(models.Model):
       stage_fold = fields.Boolean(
         'Stage Folded?',
         compute='_compute_stage_fold')
        
       @api.one
       @api.depends('stage_id.fold')
       def _compute_stage_fold(self):
         self.stage_fold = self.stage_id.fold
        
     -----
        from openerp import api
        total = fields.Float(compute='_compute_total')

@api.depends('value', 'tax')
        def _compute_total(self):
            for record in self:
                record.total = record.value + record.value * record.tax
        可以采用@api.one 让它自动循环
        @api.one
        @api.depends('value', 'tax') #定义依赖的字段,这样定义的字段值变化,对应会重新计算
        def _compute_total(self):
            self.total = self.value + self.value * self.tax       
        
   # 关联字段
      participant_nick = fields.Char(string='Nick name',
                               related='partner_id.name')
      合作伙伴名字改,该对象的昵称跟着变化
     
        
   # 搜索和写入
     class TodoTask(models.Model):
       stage_fold = fields.Boolean(
         'Stage Folded?',
         compute='_compute_stage_fold',
         # store=False) # the default
         search='_search_stage_fold',
         inverse='_write_stage_fold')
        
       @api.one
       @api.depends('stage_id.fold')
       def _compute_stage_fold(self):
         self.stage_fold = self.stage_id.fold

def _search_stage_fold(self, operator, value):
         return [('stage_id.fold', operator, value)]

def _write_stage_fold(self):
         self.stage_id.fold = self.stage_fold   
   # 为了搜索加字段
       stage_state = fields.Selection(
       related='stage_id.state',
       string='Stage State')  
      
   # 模型约束(SQL 和 Python)
     #一个用户在一时间只有一个活动的任务,加复合主键一样
     class TodoTask(models.Model):
       _sql_constraints = [
         ('todo_task_name_uniq',
         UNIQUE (name, user_id, active)',
         'Task title must be unique!')]
     
     #检查名称少于5个字符     
     class TodoTask(models.Model):
       @api.one
       @api.constrains('name')
       def _check_name_size(self):
          if len(self.name) < 5:
            raise ValidationError('Must have 5 chars!')
* 向导对象
    向导是一个动态与用户进行互动对话的模型
    TransientModel 扩展 openerp/models.py中的Model类
    #向导的数据不是固定的,系统会定期清除数据
    #向导没有任何权限限制,用户可以随意创建
    兼容osv_memory = TransientModel
    如: class procurement_compute(models.TransientModel)
         class procurement_compute(osv.osv_memory) 旧
         class procurement_compute(osv.TransientModel) 旧
        
    # 向导是通过 "ir.actions.act_window" 触发的

<record model="ir.actions.act_window" id="act_make_procurement">
            <field name="name">Procurement Request</field>
            <field name="res_model">make.procurement</field>
            <field name="src_model">product.product</field>
            <field name="view_mode">form</field>
            <field name="target">new</field>
            <field name="key2">client_action_multi</field>
        </record>
       
        可以简写:
        <act_window name="Procurement Request"
            res_model="make.procurement"
            src_model="product.product"
            view_mode="form"
            target="new"
            key2="client_action_multi"
            id="act_make_procurement"/>
       
        id 不可重名
        name 菜单名称
        src_model 上级对象
        res_model 向导对象
        view_mode 视图类型
        target 开打方式  current 当前窗口,new 新建窗口,inline 行内编辑,inlineview 内嵌视图
        key2  菜单出现位置
   
      定义向导视图
            一般两个确认事件,选择取消后窗口自动消失
      <button name="make_procurement" string="Ask New Products" type="object"  class="oe_highlight"  />
                        or
      <button string="Cancel" class="oe_link" special="cancel" />           
           
* 记录集
   一个模型的实例同时也是一个记录集的实例
   一个记录集是同一个模型的一组记录
   class AModel(Model):
    # ...
    def a_fun(self):
        self.do_something() # self 在这里就是一个记录集,会包含很多记录
        record_set = self
        record_set.do_something()

def do_something(self):
        for record in self:
           print record
     如果用了@api.one 来修饰,self 就会当前一条记录,不是记录集了

*记录集支持的操作   
    rs1 | rs2  求并集 是指两个集合的所有元素构成的集合
    rs1 +    rs2   求两个集直接连起来,不管重复
    rs1 & rs2   求交集 是指两个集合元素相同的部分构成的集合
    rs1 - rs2   求差集 是指其中一个集合中除去另一个集合相同元素以后剩余的元素构成的集合
    rs1.copy()  浅拷贝
   
    #其它记录集操作
    record in recordset   检测一个记录是否在一个记录集中
    record not in recordset      检测一个记录是否不在一个记录集中

recordset.ids 记录ID集
    recordset.ensure_one() 检测是一个记录
    recordset.exists() 若存在返回一个备份
    recordset.filtered(func) 过滤记录集
    recordset.mapped(func)
    recordset.sorted(func) 排序后
   
        # filtered()
         def filtered(self, func):
         recset.filtered(lambda record: record.company_id == user.company_id)
         只保留是当前用户公司的记录
         recset.filtered("product_id.can_be_sold")
         保留可销售的产品
    
        # sorted()
          def sorted(self, key=None, reverse=False):       
          recset.sorted(key=lambda r: r.name)
          按记录集中的名称字段升序排序
        
         #先按照安partner_id 排序,然后用name排序        
         from operator import attrgetter
         recset.sorted(key=attrgetter('partner_id', 'name'))
       
        # mapped()
         def mapped(self, func):
         recset.mapped(lambda record: record.price_unit - record.cost_price)

# 从记录集抽出 name 这一列再组成记录集
         recset.mapped('name')

# 返回合作伙伴记录集
          recset.mapped('invoice_id.partner_id')

* 特殊记录集ids属性

* 一些记录的操作
     # 得到记录的显示名称
        name_get() 方法   对应的字段是 display_name
       
* 环境操作
   # self.env 得到环境 这是一个很有用的东西
        def afun(self):
         self.env
         # or
         model.env
        
    #修改上下文
      def with_context(self, *args, **kwargs)
      self.env['res.partner'].with_context(tz=x).create(vals)
      不能用这个函数来修改当前的环境
     
    # 替换整个环境
      def with_env(self, env)
       with_env()   
     
    # 切换用户
        def sudo(self, user=SUPERUSER_ID):
        self.sudo(user.id)
        self.sudo()   # 会切换到超级用户
        # or
        self.env['res.partner'].sudo().create(vals)
        用普通用户去搜索合作伙伴
        public = env.ref('base.public_user')
        env['res.partner'].sudo(public).search([])
       
    # 得到用户
        self.env.user 得到当前用户记录
        self.env.uid  或 self._uid 得到当前用户在数据表中的id
       
    # 得到游标(这是操作数据表的基础)
        self.env.cr 或 self._cr
       
    # 得到上下文(得到状态数据)
        self.env.context 或 self._context
       
    # 得到给定模型的实例(重点业务操作)
        self.env[model_name]
        model_name ,像 res.users  res.partner等可以到后台查到
       
    # 得到xml的记录
        self.env.ref('base.main_company')
       
    # 清理环境缓存
        self.env.invalidate_all()
     
* 模型中的常用操作(v7,v8都可以调用,采用的调用形式不一样,返回的结果不一样)
    这些是模弄预定义的方法
    可以 openerp/models.py 中的 BaseModel类中的定义的方法
    其中有一段 :
    _cr = property(lambda self: self.env.cr)
    _uid = property(lambda self: self.env.uid)
    _context = property(lambda self: self.env.context)
   
    这样继承模型,就可以用 self._cr  self._uid  self._context
   
    # 基本方法create, search, search_count, read, search_read, browse, write, unlink,copy
   
        -----
        create:创建一条记录
       
        @api.model
        @api.returns('self', lambda value: value.id)
        def create(self, vals):
            pass...
        vals 是一个字典数据类型 {'key':'value'}
       
        v8调用 create(vals)  返回创建的记录
        v7调用 create(cr, uid, vals, context=none)返回创建记录的id
        例子:
            v8:self.env.get('res.users').create({'name': 'New name'})
            v7:self.pool['stock.transfer_details'].create(cr, uid, {'picking_id': len(picking) and picking[0] or False}, context)
       
        ---------
        write:写入记录
       
        @api.multi
        def write(self, vals): 
        def _write(self, cr, user, ids, vals, context=None)
       
        vals:是一个字典数据类型 {'key':'value'}
       
        v8调用 write(vals)  返回True
        v7调用 write(cr, uid, vals, context=none)
        例子:
            v8:self.write({'x': 1, 'y': 2, 'z': 4})
            v7:self.write(cr, uid, processed_ids, {'processed': 'true'}, context=context)
    
        -----------       
        search:指定条件搜索记录
       
        @api.returns('self',
        upgrade=lambda self, value, args, offset=0, limit=None, order=None, count=False: value if count else self.browse(value),
        downgrade=lambda self, value, args, offset=0, limit=None, order=None, count=False: value if count else value.ids)
        def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
            pass...
           
        args 是搜索条件 是domain表达式
        offset 记录第几个 (int)
        limit 返回几个(int)
        order 指定排序字段(str)
        count 为真,则只返回记录集总数 (bool)
           
        v8调用 search(args[, offset=0][, limit=None][, order=None][, count=False])  返回记录集
        v7调用 search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False) 返回记录集ids
        例子:
            v8: self.env.get('res.users').search([('is_company', '=', True)])
            v7:self.pool.get('stock.config.settings').search(cr, uid, [], limit=1, order='id DESC', context=context)
                      
        -----------       
        search_count:指定条件搜索记录数
       
        def search_count(self, cr, user, args, context=None):
            pass...
               
        args 是搜索条件 是domain表达式
       
        v8调用 search(args) 
        v7调用 search_count(self, cr, user, args, context=None)
        例子:
            v8: self.env.get('res.users').search_count([('is_company', '=', True)])
            v7:self.pool('account.move.line').search_count(cr, uid, [('asset_id', '=', asset_id)], context=context)
           
        -------
        read:读取记录集中指定字段 返回字典列表(记录)
       
        @api.v7
        def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
            pass...
        若有多个返回记录的字典列表,若一个就返回一个记录,没有就返回false   
       
        @api.v8
        def read(self, fields=None, load='_classic_read'):
            pass...
        若有多个返回记录的字典列表,没有就返回空列表[]   
           
        fields: 字段列表,默认是全部字段(list)   
       
        v8调用 read(fields)       
        v7调用 read(self, cr, user, ids, fields=None, context=None)
        例子:
            v8:self.env['res.users'].read([], ['name'])
            v7:self.read(cr, uid, ids, ['product_qty'])
       
        ----------------
        search_read:搜索并读取指定字段,合了search()和read()方法
       
        resolve_o2m_commands_to_record_dicts = resolve_2many_commands
        def search_read(self, cr, uid, domain=None, fields=None, offset=0, limit=None, order=None, context=None):
            pass...
           
        domain:条件表达式
        fields: 字段列表,默认是全部字段(list)
        offset 记录第几个 (int)
        limit 返回几个(int)
        order 指定排序字段(str)
   
        v8调用 self.search_read(domain=None, fields=None)
        v7调用 self.search_read(self, cr, uid, domain=None, fields=None, offset=0, limit=None, order=None, context=None)
        例子:
            v8: self.env['res.users'].search_read([], ['name'])
            v7: self.pool.get('stock.picking.type').search_read(cr, uid, [], ['sequence'], order='sequence desc')
           
        ----------------
        browse: 指定ids搜索记录集
       
        @api.v7
        def browse(self, cr, uid, arg=None, context=None):
   
        @api.v8
        def browse(self, arg=None):
       
        args 记录的ids (list)
       
        v8调用 self.browse(args)
        v7调用 self.browse(self, cr, uid, arg=None, context=None)
        例子:
            v8: self.env['res.users'].self.browse([1, 2, 3])
            v7: self.pool.get('stock.quant').browse(cr, uid, quant_ids, context=context)
       
        ---------------
        unlink:记录删除
       
        def unlink(self, cr, uid, ids, context=None):
       
        ids:记录ID集 (list)
       
        v8调用 self.unlink(ids)  返回一个新记录
        v7调用 self.unlink(self, cr, uid, ids, context=None) 返回一个新记录id
       
        例子:
            v8: self.unlink([operation_id])
            v7: self.unlink(cr, uid, [operation_id], context=context)
        --------------
        copy:复制记录
        分为记录拷贝和记录集拷贝
        记录拷贝:
        @api.one
        记录集拷贝
        @api.multi
       
        @api.returns('self', lambda value: value.id)
        def copy(self, cr, uid, id, default=None, context=None):
       
        v8调用 self.copy(args)  返回一个新记录
        v7调用 self.copy(self, cr, uid, id, default=None, context=None 返回一个新记录id
       
        例子:
            v8: self.copy(move.id)
            v7: self.copy(cr, uid, move.id, default_val)
       
       
    # 得到指定字段默认值:
        @api.model
        def default_get(self, fields_list):
        返回指定字段默认的字典列表

v8调用 default_get(fields_list) 
        v7调用 default_get(self, cr, uid, fields, context=None)
       
        例子:
            v8: self.default_get({'reception_steps', 'delivery_steps'})
            v7: self.default_get(cr, uid, {'reception_steps', 'delivery_steps'})
           
    # 得到记录的显示名:
        @api.multi
        def name_get(self):
            pass...
       
        这个方法会覆盖:
        https://github.com/odoo/odoo/blob/8.0/addons/event/event.py#L194
           
        v8调用 self.name_get() 
        v7调用 self.name_get(cr, user, ids, context=null) 一般重写了,加入了ids
       
        例子:
            v8:
            v7:
           
    # 创建只有display_name的记录
        @api.model
        def name_create(self, name):
        返回创建好的记录的 [(id,name)]
       
        v8调用 self.name_create() 
        v7调用 self.name_create(cr, user, name, context=null) 一般重写了,加入了ids
       
    # 根据display_name来搜索
        @api.model
        def name_search(self, name='', args=None, operator='ilike', limit=100):
            pass...
       
        name:给定比较的name (str)
        args:domain:条件表达式
        operator: name用来的比较操作符
        limit:限定返回记录数目
       
        v8调用 self.name_search(name[,args][,operator][,limit]) 
        v7调用 self.name_search(cr, user,name[,args][,operator][,limit], context=null) 
       
    # 得到指定字段特定语言的字符串
        def read_string(self, cr, uid, id, langs, fields=None, context=None):   
       
    # 写入批定字段特定语言的字符串   
        def write_string(self, cr, uid, id, langs, vals, context=None):
       
    # 清除记录的缓存
        def clear_caches(self):   
       
    # 按分组列表记录
        def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True):   
       
        cr: 数据游标
        uid: 当前用户id
        domain: domain:条件表达式
        fields: 列出来显示的字段 (list)
        groupby: 列出用来分组的字段(list)
        offset: 结果集的起点,第几条开始(int)
        limit: 最多显示几条(int)
        context: 上下文
        orderby: 字段排序列表(list)
        lazy: 延迟加载(bool)
        返回字典列表
       
       
    # 得到字段的权限
        def check_field_access_rights(self, cr, user, operation, fields, context=None):

# 特殊字段操作方法:perm_read, perm_write
        def perm_read(self, cr, uid, ids)
        def perm_write(self, cr, uid, ids, fields)
       
    #字段(fields)和视图(views)操作方法:fields_get, distinct_field_get, fields_view_get
        def fields_get(self, cr, user, allfields=None, context=None, write_access=True, attributes=None)
        def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False)
        def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None)
       
   
    # 记录写
        @api.one
        ...
        self.write({'key': value })
        # or
        record.write({'key': value})   
        写入没有返回值
        @api.multi
        def do_mass_update(self):
            self.ensure_one()
            if not (self.new_deadline or self.new_user_id):
                raise exceptions.ValidationError('No data to update!')
            # else:
            _logger.debug('Mass update on Todo Tasks %s',
                self.task_ids.ids)
            if self.new_deadline:
                self.task_ids.write({'date_deadline': self.new_deadline})
            if self.new_user_id:
                self.task_ids.write({'user_id': self.new_user_id.id})
            return True
           
        self.ensure_one() 检测单例
       
    # 检查记录对象存在 exists()
        if not record.exists():
            raise Exception("The record has been deleted")
   
    # 外id引用
        >>> env.ref('base.group_public')
        res.groups(2)
   
    #保证记录单例 ensure_one
        records.ensure_one()
        # is equivalent to but clearer than:
        assert len(records) == 1, "Expected singleton"
   
    # 记录集写
        @api.multi
        ...
        self.write({'key': value })
        # It will write on all record.
        self.line_ids.write({'key': value })   
       
    # Many2many One2Many的行为
        self.line_ids.create({'name': 'Tho'})   #
        self.line_ids.create({'name': 'Tho', 'order_id': self.id})  #
        self.line_ids.write({'name': 'Tho'})    # 保存所有的相关记录
       
    # 删除
        >>> rec = Partner.search([('name', '=', 'ACME')])
        >>> rec.unlink()
       
    # 抛出异常终止
        raise Exception(
                "You can not export the column ID of model %s, because the "
                "table %s is not an ordinary table."
                % (self._name, self._table))
        这个方式也可用来调试代码变量       
   
       
* 使用数据库操作游标
    def my_fun(self):
    cursor = self._cr
    # or
    self.env.cr
   
* 保用线程
    with Environment.manage():  # class function
        env = Environment(cr, uid, context)
       
* 打印时间处理,主要是时区
    dt = datetime.datetime.strptime(str, '%Y-%m-%d %H:%M:%S')
    dt = dt.replace(tzinfo=pytz.utc).astimezone(pytz.timezone('Asia/Shanghai'))
    return dt.strftime(fmt.encode('utf-8')).decode('utf-8')
    这个主要的是解决打印时间的显示的和实际时间的差别,