Merge pull request #1790 from thanhchatvn/patch-2
authorFabien Pinckaers <fp@openerp.com>
Tue, 19 Aug 2014 11:05:28 +0000 (13:05 +0200)
committerFabien Pinckaers <fp@openerp.com>
Tue, 19 Aug 2014 11:05:28 +0000 (13:05 +0200)
Update vi.po

18 files changed:
addons/account/account_move_line.py
addons/mrp_operations/mrp_operations.py
addons/point_of_sale/report/pos_invoice.py
addons/point_of_sale/static/src/js/db.js
addons/point_of_sale/static/src/js/models.js
addons/point_of_sale/static/src/js/screens.js
addons/portal_sale/portal_sale.py
addons/product/product.py
addons/project_timesheet/project_timesheet.py
addons/purchase/purchase_view.xml
addons/sale_layout/models/sale_layout.py
addons/website_forum_doc/static/src/css/website_forum_doc.css [new file with mode: 0644]
addons/website_forum_doc/views/doc.xml
addons/website_forum_doc/views/website_doc.xml
addons/website_quote/models/order.py
openerp/addons/base/ir/ir_qweb.py
openerp/models.py
openerp/osv/fields.py

index c513261..642c209 100644 (file)
@@ -1297,7 +1297,7 @@ class account_move_line(osv.osv):
                     self.create(cr, uid, data, context)
             del vals['account_tax_id']
 
-        if check and not context.get('novalidate') and ((not context.get('no_store_function')) or journal.entry_posted):
+        if check and not context.get('novalidate') and (context.get('recompute', True) or journal.entry_posted):
             tmp = move_obj.validate(cr, uid, [vals['move_id']], context)
             if journal.entry_posted and tmp:
                 move_obj.button_validate(cr,uid, [vals['move_id']], context)
index c14b755..b67f7eb 100644 (file)
@@ -289,7 +289,7 @@ class mrp_production(osv.osv):
         for po in self.browse(cr, uid, ids, context=context):
             if po.allow_reorder:
                 continue
-            todo = po.move_lines
+            todo = list(po.move_lines)
             dt = datetime.strptime(po.date_start,'%Y-%m-%d %H:%M:%S')
             while todo:
                 l = todo.pop(0)
index f83da57..6007b00 100644 (file)
@@ -32,9 +32,11 @@ class PosInvoiceReport(osv.AbstractModel):
         report = report_obj._get_report_from_name(cr, uid, 'account.report_invoice')
         selected_orders = posorder_obj.browse(cr, uid, ids, context=context)
 
+        ids_to_print = []
         invoiced_posorders_ids = []
         for order in selected_orders:
             if order.invoice_id:
+                ids_to_print.append(order.invoice_id.id)
                 invoiced_posorders_ids.append(order.id)
 
         not_invoiced_orders_ids = list(set(ids) - set(invoiced_posorders_ids))
@@ -44,7 +46,7 @@ class PosInvoiceReport(osv.AbstractModel):
             raise osv.except_osv(_('Error!'), _('No link to an invoice for %s.' % ', '.join(not_invoiced_orders_names)))
 
         docargs = {
-            'doc_ids': ids,
+            'doc_ids': ids_to_print,
             'doc_model': report.model,
             'docs': selected_orders,
         }
index 06a6a6b..ef6a29f 100644 (file)
@@ -128,7 +128,7 @@ function openerp_pos_db(instance, module){
             this.cache[store] = data;
         },
         _product_search_string: function(product){
-            var str = '' + product.id + ':' + product.name;
+            var str = '' + product.id + ':' + product.display_name;
             if(product.ean13){
                 str += '|' + product.ean13;
             }
index 42749f1..f41f03a 100644 (file)
@@ -176,6 +176,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
             domain: function(self){ return [['state','=','opened'],['user_id','=',self.session.uid]]; },
             loaded: function(self,pos_sessions){
                 self.pos_session = pos_sessions[0]; 
+                self.pos_session_id = parseInt(self.pos_session.name.split('/')[1]);
 
                 var orders = self.db.get_orders();
                 for (var i = 0; i < orders.length; i++) {
@@ -240,7 +241,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
                      'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description',
                      'product_tmpl_id'],
             domain:  function(self){ return [['sale_ok','=',true],['available_in_pos','=',true]]; },
-            context: function(self){ return { pricelist: self.pricelist.id }; },
+            context: function(self){ return { pricelist: self.pricelist.id, display_default_code: false }; },
             loaded: function(self, products){
                 self.db.add_products(products);
             },
@@ -712,7 +713,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
                 unit_name:          this.get_unit().name,
                 price:              this.get_unit_price(),
                 discount:           this.get_discount(),
-                product_name:       this.get_product().name,
+                product_name:       this.get_product().display_name,
                 price_display :     this.get_display_price(),
                 price_with_tax :    this.get_price_with_tax(),
                 price_without_tax:  this.get_price_without_tax(),
@@ -856,6 +857,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
     module.Order = Backbone.Model.extend({
         initialize: function(attributes){
             Backbone.Model.prototype.initialize.apply(this, arguments);
+            this.pos = attributes.pos; 
+            this.sequence_number = this.pos.pos_session.sequence_number++;
             this.uid =     this.generateUniqueId();
             this.set({
                 creationDate:   new Date(),
@@ -864,20 +867,28 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
                 name:           "Order " + this.uid,
                 client:         null,
             });
-            this.pos = attributes.pos; 
             this.selected_orderline   = undefined;
             this.selected_paymentline = undefined;
             this.screen_data = {};  // see ScreenSelector
             this.receipt_type = 'receipt';  // 'receipt' || 'invoice'
             this.temporary = attributes.temporary || false;
-            this.sequence_number = this.pos.pos_session.sequence_number++;
             return this;
         },
         is_empty: function(){
             return (this.get('orderLines').models.length === 0);
         },
+        // Generates a public identification number for the order.
+        // The generated number must be unique and sequential. They are made 12 digit long
+        // to fit into EAN-13 barcodes. 
         generateUniqueId: function() {
-            return new Date().getTime();
+            function zero_pad(num,size){
+                var s = ""+num;
+                while (s.length < size) {
+                    s = "0" + s;
+                }
+                return s;
+            }
+            return zero_pad(this.pos.pos_session_id,6) + zero_pad(this.sequence_number,6);
         },
         addOrderline: function(line){
             if(line.order){
index d0d91a2..8610460 100644 (file)
@@ -492,7 +492,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         },
         get_product_name: function(){
             var product = this.get_product();
-            return (product ? product.name : undefined) || 'Unnamed Product';
+            return (product ? product.display_name : undefined) || 'Unnamed Product';
         },
         get_product_price: function(){
             var product = this.get_product();
index e7078bc..31a2e1f 100644 (file)
@@ -138,7 +138,7 @@ class account_invoice(osv.Model):
         user = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context)
         if user.share:
             return self.pool['ir.actions.act_window'].for_xml_id(cr, uid, 'portal_sale', 'portal_action_invoices', context=context)
-        return super(sale_order, self).get_formview_action(cr, uid, id, context=context)
+        return super(account_invoice, self).get_formview_action(cr, uid, id, context=context)
 
 
 class mail_mail(osv.osv):
index 90a3c3a..a728429 100644 (file)
@@ -979,7 +979,7 @@ class product_product(osv.osv):
 
         def _name_get(d):
             name = d.get('name','')
-            code = d.get('default_code',False)
+            code = context.get('display_default_code', True) and d.get('default_code',False) or False
             if code:
                 name = '[%s] %s' % (code,name)
             return (d['id'], name)
index 692249b..cfce80c 100644 (file)
@@ -137,7 +137,7 @@ class project_work(osv.osv):
                 amount = vals_line['unit_amount']
                 prod_id = vals_line['product_id']
                 unit = False
-                context = dict(context, no_store_function=False)
+                context = dict(context, recompute=True)
                 timeline_id = timesheet_obj.create(cr, uid, vals_line, context=context)
 
                 # Compute based on pricetype
index 9228e71..188bd20 100644 (file)
             <field name="name">product.template.purchase.button.inherit</field>
             <field name="model">product.template</field>
             <field name="inherit_id" ref="product.product_template_only_form_view"/>
+            <field name="groups_id" eval="[(4, ref('purchase.group_purchase_user'))]"/>
             <field name="arch" type="xml">
                 <xpath expr="//div[@name='buttons']" position="inside">
                    <button class="oe_inline oe_stat_button" name="action_view_purchases" type="object" 
-                       groups="purchase.group_purchase_user" icon="fa-shopping-cart">
+                       icon="fa-shopping-cart">
                        <field string="Purchases" name="purchase_count" widget="statinfo"/>
                    </button>
                 </xpath>
             <field name="name">product.product.purchase.button.inherit</field>
             <field name="model">product.product</field>
             <field name="inherit_id" ref="product.product_normal_form_view"/>
+            <field name="groups_id" eval="[(4, ref('purchase.group_purchase_user'))]"/>
             <field name="arch" type="xml">
                 <xpath expr="//div[@name='buttons']" position="inside">
                    <button class="oe_inline oe_stat_button" name="%(action_purchase_line_product_tree)d" type="action" 
-                       groups="purchase.group_purchase_user" icon="fa-shopping-cart">
+                       icon="fa-shopping-cart">
                        <field string="Purchases" name="purchase_count" widget="statinfo"/>
                    </button>
                 </xpath>
index 7047a63..b5c996a 100644 (file)
@@ -75,18 +75,16 @@ class AccountInvoice(osv.Model):
         return grouplines(self, ordered_lines, sortkey)
 
 
+import openerp
+
 class AccountInvoiceLine(osv.Model):
     _inherit = 'account.invoice.line'
-    _columns = {
-        'sale_layout_cat_id': fields.many2one('sale_layout.category',
-                                              string='Section'),
-        'categ_sequence': fields.related('sale_layout_cat_id',
-                                         'sequence', type='integer',
-                                         string='Layout Sequence', store=True)
-        #  Store is intentionally set in order to keep the "historic" order.
-    }
     _order = 'invoice_id, categ_sequence, sequence, id'
 
+    sale_layout_cat_id = openerp.fields.Many2one('sale_layout.category', string='Section')
+    categ_sequence = openerp.fields.Integer(related='sale_layout_cat_id.sequence',
+                                            string='Layout Sequence', store=True)
+
 
 class SaleOrder(osv.Model):
     _inherit = 'sale.order'
diff --git a/addons/website_forum_doc/static/src/css/website_forum_doc.css b/addons/website_forum_doc/static/src/css/website_forum_doc.css
new file mode 100644 (file)
index 0000000..c3d0523
--- /dev/null
@@ -0,0 +1,4 @@
+.oe_toc_content img {
+  max-width: 600px;
+  height: auto !important;
+}
index d5f309a..2e21f0b 100644 (file)
@@ -3,7 +3,6 @@
     <data>
 
         <!-- DOCUMENTATION TOC VIEWS -->
-
         <record id="view_documentation_toc_list" model="ir.ui.view">
             <field name="name">forum.documentation.toc.list</field>
             <field name="model">forum.documentation.toc</field>
             name="Documentation ToC" action="action_documentation_toc" sequence="20"/>
 
 
-        <record id="view_documentation_toc_list" model="ir.ui.view">
-            <field name="name">forum.documentation.toc.list</field>
-            <field name="model">forum.documentation.toc</field>
-            <field name="arch" type="xml">
-                <tree string="Documentation TOC" editable="bottom">
-                    <field name="sequence" widget="handle"/>
-                    <field name="name"/>
-                    <field name="parent_id"/>
-                </tree>
-            </field>
-        </record>
-        <record id="action_documentation_toc" model="ir.actions.act_window">
-            <field name="name">Documentation</field>
-            <field name="res_model">forum.documentation.toc</field>
-            <field name="view_type">form</field>
-            <field name="view_mode">tree</field>
-        </record>
-        <menuitem id="menu_documentation"
-            parent="website_forum.menu_website_forum" groups="base.group_user"
-            name="Documentation ToC" action="action_documentation_toc" sequence="20"/>
-
-
         <!-- Project Task Kanban View -->
         <record model="ir.ui.view" id="view_forum_post_kanban">
             <field name="name">forum.post.kanban</field>
index 7e7e331..06cdece 100644 (file)
@@ -2,6 +2,13 @@
 <openerp>
     <data>
 
+        <!-- Front-end assets: custom css -->
+        <template id="assets_frontend" inherit_id="website.assets_frontend" name="Shop">
+            <xpath expr="." position="inside">
+                <link rel='stylesheet' href='/website_forum_doc/static/src/css/website_forum_doc.css'/>
+            </xpath>
+        </template>
+
         <!-- Layout add nav and footer -->
         <template id="header_footer_custom" inherit_id="website.footer_default"
             name="Footer Documentation Link">
                     <div class="row">
                         <div class="col-sm-9">
                             <h1 class="page-header" t-field="post.name"/>
-                            <blockquote t-if="bool(post.content)">
+                            <blockquote class="oe_toc_content" t-if="bool(post.content)">
                                 <t t-raw="post.content"/>
                             </blockquote>
 
index c9d72db..3754eed 100644 (file)
@@ -51,7 +51,7 @@ class sale_quote_line(osv.osv):
         'quote_id': fields.many2one('sale.quote.template', 'Quotation Template Reference', required=True, ondelete='cascade', select=True),
         'name': fields.text('Description', required=True, translate=True),
         'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True),
-        'website_description': fields.html('Line Description', translate=True),
+        'website_description': fields.related('product_id', 'product_tmpl_id', 'quote_description', string='Line Description', type='html', translate=True),
         'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
         'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Discount')),
         'product_uom_qty': fields.float('Quantity', required=True, digits_compute= dp.get_precision('Product UoS')),
@@ -70,11 +70,30 @@ class sale_quote_line(osv.osv):
         vals.update({
             'price_unit': product_obj.list_price,
             'product_uom_id': product_obj.uom_id.id,
-            'website_description': product_obj.website_description,
+            'website_description': product_obj and (product_obj.quote_description or product_obj.website_description) or '',
             'name': name,
         })
         return {'value': vals}
 
+    def _inject_quote_description(self, cr, uid, values, context=None):
+        values = dict(values or {})
+        if not values.get('website_description') and values.get('product_id'):
+            product = self.pool['product.product'].browse(cr, uid, values['product_id'], context=context)
+            values['website_description'] = product.quote_description or product.website_description or ''
+        return values
+
+    def create(self, cr, uid, values, context=None):
+        values = self._inject_quote_description(cr, uid, values, context)
+        ret = super(sale_quote_line, self).create(cr, uid, values, context=context)
+        # hack because create don t make the job for a related field
+        if values.get('website_description'):
+            self.write(cr, uid, ret, {'website_description': values['website_description']}, context=context)
+        return ret
+
+    def write(self, cr, uid, ids, values, context=None):
+        values = self._inject_quote_description(cr, uid, values, context)
+        return super(sale_quote_line, self).write(cr, uid, ids, values, context=context)
+
 
 class sale_order_line(osv.osv):
     _inherit = "sale.order.line"
@@ -84,19 +103,23 @@ class sale_order_line(osv.osv):
         'option_line_id': fields.one2many('sale.order.option', 'line_id', 'Optional Products Lines'),
     }
 
-    def _inject_website_description(self, cr, uid, values, context=None):
+    def _inject_quote_description(self, cr, uid, values, context=None):
         values = dict(values or {})
         if not values.get('website_description') and values.get('product_id'):
             product = self.pool['product.product'].browse(cr, uid, values['product_id'], context=context)
-            values['website_description'] = product.website_description
+            values['website_description'] = product.quote_description or product.website_description
         return values
 
     def create(self, cr, uid, values, context=None):
-        values = self._inject_website_description(cr, uid, values, context)
-        return super(sale_order_line, self).create(cr, uid, values, context=context)
+        values = self._inject_quote_description(cr, uid, values, context)
+        ret = super(sale_order_line, self).create(cr, uid, values, context=context)
+        # hack because create don t make the job for a related field
+        if values.get('website_description'):
+            self.write(cr, uid, ret, {'website_description': values['website_description']}, context=context)
+        return ret
 
     def write(self, cr, uid, ids, values, context=None):
-        values = self._inject_website_description(cr, uid, values, context)
+        values = self._inject_quote_description(cr, uid, values, context)
         return super(sale_order_line, self).write(cr, uid, ids, values, context=context)
 
 
@@ -222,7 +245,7 @@ class sale_quote_option(osv.osv):
         product_obj = self.pool.get('product.product').browse(cr, uid, product, context=context)
         vals.update({
             'price_unit': product_obj.list_price,
-            'website_description': product_obj.product_tmpl_id.website_description,
+            'website_description': product_obj.product_tmpl_id.quote_description,
             'name': product_obj.name,
             'uom_id': product_obj.product_tmpl_id.uom_id.id,
         })
@@ -252,7 +275,7 @@ class sale_order_option(osv.osv):
         product_obj = self.pool.get('product.product').browse(cr, uid, product, context=context)
         vals.update({
             'price_unit': product_obj.list_price,
-            'website_description': product_obj.product_tmpl_id.website_description,
+            'website_description': product_obj and (product_obj.quote_description or product_obj.website_description),
             'name': product_obj.name,
             'uom_id': product_obj.product_tmpl_id.uom_id.id,
         })
@@ -260,6 +283,9 @@ class sale_order_option(osv.osv):
 
 class product_template(osv.Model):
     _inherit = "product.template"
+
     _columns = {
-        'website_description': fields.html('Description for the website'),
+        'website_description': fields.html('Description for the website'), # hack, if website_sale is not installed
+        'quote_description': fields.html('Description for the quote'),
     }
+
index 01400de..1ef2212 100644 (file)
@@ -1015,7 +1015,6 @@ class AssetsBundle(object):
     # Use this:
     #       sudo gem install compass --pre
     cmd_sass = ['sass', '--stdin', '-t', 'compressed', '--unix-newlines', '--compass', '-r', 'bootstrap-sass']
-    cache = openerp.tools.lru.LRU(32)
     rx_css_import = re.compile("(@import[^;{]+;?)", re.M)
     rx_sass_import = re.compile("""(@import\s?['"]([^'"]+)['"])""")
     rx_css_split = re.compile("\/\*\! ([a-f0-9-]+) \*\/")
@@ -1115,21 +1114,15 @@ class AssetsBundle(object):
         return hashlib.sha1(check).hexdigest()
 
     def js(self):
-        key = 'js_%s' % self.xmlid
-        if key in self.cache and self.cache[key][0] != self.version:
-            # Invalidate cache on version mismach
-            self.cache.pop(key)
-        if key not in self.cache:
-            content =';\n'.join(asset.minify() for asset in self.javascripts)
-            self.cache[key] = (self.version, content)
-        return self.cache[key][1]
+        content = self.get_cache('js')
+        if content is None:
+            content = ';\n'.join(asset.minify() for asset in self.javascripts)
+            self.set_cache('js', content)
+        return content
 
     def css(self):
-        key = 'css_%s' % self.xmlid
-        if key in self.cache and self.cache[key][0] != self.version:
-            # Invalidate cache on version mismach
-            self.cache.pop(key)
-        if key not in self.cache:
+        content = self.get_cache('css')
+        if content is None:
             self.compile_sass()
             content = '\n'.join(asset.minify() for asset in self.stylesheets)
 
@@ -1149,9 +1142,32 @@ class AssetsBundle(object):
             content = u'\n'.join(matches)
             if self.css_errors:
                 return content
-            self.cache[key] = (self.version, content)
+            self.set_cache('css', content)
 
-        return self.cache[key][1]
+        return content
+
+    def get_cache(self, type):
+        content = None
+        domain = [('url', '=', '/web/%s/%s/%s' % (type, self.xmlid, self.version))]
+        bundle = self.registry['ir.attachment'].search_read(self.cr, self.uid, domain, ['datas'], context=self.context)
+        if bundle:
+            content = bundle[0]['datas'].decode('base64')
+        return content
+
+    def set_cache(self, type, content):
+        ira = self.registry['ir.attachment']
+        url_prefix = '/web/%s/%s/' % (type, self.xmlid)
+        # Invalidate previous caches
+        oids = ira.search(self.cr, self.uid, [('url', '=like', url_prefix + '%')], context=self.context)
+        if oids:
+            ira.unlink(self.cr, openerp.SUPERUSER_ID, oids, context=self.context)
+        url = url_prefix + self.version
+        ira.create(self.cr, openerp.SUPERUSER_ID, dict(
+                    datas=content.encode('utf8').encode('base64'),
+                    type='binary',
+                    name=url,
+                    url=url,
+                ), context=self.context)
 
     def css_message(self, message):
         return """
index 8daaa57..618303a 100644 (file)
@@ -1304,10 +1304,6 @@ class BaseModel(object):
 
         # convert default values to the expected format
         result = self._convert_to_write(result)
-        for key, val in result.items():
-            if isinstance(val, NewId):
-                del result[key]                 # ignore new records in defaults
-
         return result
 
     def add_default_value(self, field):
@@ -3152,11 +3148,11 @@ class BaseModel(object):
             pass
 
         # check the cache, and update it if necessary
-        if field not in self._cache:
+        if not self._cache.contains(field):
             for values in result:
                 record = self.browse(values.pop('id'))
                 record._cache.update(record._convert_to_cache(values, validate=False))
-            if field not in self._cache:
+            if not self._cache.contains(field):
                 e = AccessError("No value found for %s.%s" % (self, field.name))
                 self._cache[field] = FailedValue(e)
 
@@ -3976,15 +3972,10 @@ class BaseModel(object):
 
             record_id = tocreate[table].pop('id', None)
 
-            # When linking/creating parent records, force context without 'no_store_function' key that
-            # defers stored functions computing, as these won't be computed in batch at the end of create().
-            parent_context = dict(context)
-            parent_context.pop('no_store_function', None)
-
             if record_id is None or not record_id:
-                record_id = self.pool[table].create(cr, user, tocreate[table], context=parent_context)
+                record_id = self.pool[table].create(cr, user, tocreate[table], context=context)
             else:
-                self.pool[table].write(cr, user, [record_id], tocreate[table], context=parent_context)
+                self.pool[table].write(cr, user, [record_id], tocreate[table], context=context)
 
             updates.append((self._inherits[table], '%s', record_id))
 
@@ -4109,7 +4100,13 @@ class BaseModel(object):
         # check Python constraints
         recs._validate_fields(vals)
 
-        if not context.get('no_store_function', False):
+        # invalidate and mark new-style fields to recompute
+        modified_fields = list(vals)
+        if self._log_access:
+            modified_fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
+        recs.modified(modified_fields)
+
+        if context.get('recompute', True):
             result += self._store_get_values(cr, user, [id_new],
                 list(set(vals.keys() + self._inherits.values())),
                 context)
@@ -4119,15 +4116,10 @@ class BaseModel(object):
                 if not (model_name, ids, fields2) in done:
                     self.pool[model_name]._store_set_values(cr, user, ids, fields2, context)
                     done.append((model_name, ids, fields2))
-
             # recompute new-style fields
-            modified_fields = list(vals)
-            if self._log_access:
-                modified_fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
-            recs.modified(modified_fields)
             recs.recompute()
 
-        if self._log_create and not (context and context.get('no_store_function', False)):
+        if self._log_create and context.get('recompute', True):
             message = self._description + \
                 " '" + \
                 self.name_get(cr, user, [id_new], context=context)[0][1] + \
@@ -4272,7 +4264,7 @@ class BaseModel(object):
                         cr.execute('update "' + self._table + '" set ' + \
                             '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
 
-        # invalidate the cache for the modified fields
+        # invalidate and mark new-style fields to recompute
         self.browse(cr, uid, ids, context).modified(fields)
 
         return True
@@ -5139,11 +5131,13 @@ class BaseModel(object):
     def _convert_to_write(self, values):
         """ Convert the `values` dictionary into the format of :meth:`write`. """
         fields = self._fields
-        return dict(
-            (name, fields[name].convert_to_write(value))
-            for name, value in values.iteritems()
-            if name in self._fields
-        )
+        result = {}
+        for name, value in values.iteritems():
+            if name in fields:
+                value = fields[name].convert_to_write(value)
+                if not isinstance(value, NewId):
+                    result[name] = value
+        return result
 
     #
     # Record traversal and update
@@ -5678,6 +5672,12 @@ class RecordCache(MutableMapping):
     def __init__(self, records):
         self._recs = records
 
+    def contains(self, field):
+        """ Return whether `records[0]` has a value for `field` in cache. """
+        if isinstance(field, basestring):
+            field = self._recs._fields[field]
+        return self._recs.id in self._recs.env.cache[field]
+
     def __contains__(self, field):
         """ Return whether `records[0]` has a regular value for `field` in cache. """
         if isinstance(field, basestring):
index 2126fff..5bac088 100644 (file)
@@ -684,7 +684,7 @@ class one2many(_column):
         result = []
         context = dict(context or {})
         context.update(self._context)
-        context['no_store_function'] = True
+        context['recompute'] = False    # recomputation is done by outer create/write
         if not values:
             return
         obj = obj.pool[self._obj]