예제 #1
0
 def onSuccess(self, fields, REQUEST=None, loopstop=False):
     """
     saves data.
     """
     now = tzawarenow()
     soup = self.get_soup()
     modified_fields = list()
     iid = self.REQUEST.cookies.get('PFGSOUP_POST', None)
     if iid:
         iid = int(iid)
         data = soup.get(iid)
     else:
         data = OOBTNode()
     for field in fields:
         if field.isLabel():
             continue
         field_name = field.getFieldFormName()
         if field.isFileField():
             file_value = REQUEST.form.get('%s_file' % field_name)
             raise NotImplementedError('FileField Not Yet Done')
         value = REQUEST.form.get(field_name, '')
         if iid:
             if data.attrs.get(field_name, None) == value:
                 continue
             modified_fields.append(field_name)
         data.attrs[field_name] = value
     sm = getSecurityManager()
     if iid:
         if modified_fields:
             data.attrs['_auto_last_modified'] = now
             log = {}
             log['user'] = sm.getUser().getId()
             log['date'] = now
             log['fields'] = modified_fields
             if '_auto_log' not in data.attrs:
                 data.attrs['_auto_log'] = PersistentList()
             data.attrs['_auto_log'].append(log)
         self.REQUEST.response.expireCookie('PFGSOUP_POST', path='/')
         # XXX redirect to table
         # self.REQUEST.response.redirect(...)
         return
     data.attrs['_auto_created'] = now
     data.attrs['_auto_last_modified'] = now
     data.attrs['_auto_userid'] = sm.getUser().getId()
     data.attrs['_auto_log'] = PersistentList()
     soup.add(data)
예제 #2
0
 def onSuccess(self, fields, REQUEST=None, loopstop=False):
     """
     saves data.
     """
     now = tzawarenow()
     soup = self.get_soup()
     modified_fields = list()
     iid = self.REQUEST.cookies.get('PFGSOUP_POST', None)
     if iid:
         iid = int(iid)
         data = soup.get(iid)
     else:
         data = OOBTNode()
     for field in fields:
         if field.isLabel():
             continue
         field_name = field.getFieldFormName()
         if field.isFileField():
             file_value = REQUEST.form.get('%s_file' % field_name)
             raise NotImplementedError('FileField Not Yet Done')
         value = REQUEST.form.get(field_name, '')
         if iid:
             if data.attrs.get(field_name, None) == value:
                 continue
             modified_fields.append(field_name)
         data.attrs[field_name] = value
     sm = getSecurityManager()
     if iid:
         if modified_fields:
             data.attrs['_auto_last_modified'] = now
             log = {}
             log['user'] = sm.getUser().getId()
             log['date'] = now
             log['fields'] = modified_fields
             if '_auto_log' not in data.attrs:
                 data.attrs['_auto_log'] = PersistentList()
             data.attrs['_auto_log'].append(log)
         self.REQUEST.response.expireCookie('PFGSOUP_POST', path='/')
         # XXX redirect to table
         # self.REQUEST.response.redirect(...)
         return
     data.attrs['_auto_created'] = now
     data.attrs['_auto_last_modified'] = now
     data.attrs['_auto_userid'] = sm.getUser().getId()
     data.attrs['_auto_log'] = PersistentList()
     soup.add(data)
예제 #3
0
    def test_OOBTNode(self):
        # Based on OOBTree as storage
        oobtnode = OOBTNode(name='oobtnode')

        # Interface check
        self.assertTrue(IZODBNode.providedBy(oobtnode))

        # Storage check
        self.assertTrue(isinstance(oobtnode.storage, OOBTodict))
        self.assertTrue(isinstance(oobtnode._storage, OOBTodict))

        # Structure check
        root = self.open()
        root[oobtnode.__name__] = oobtnode
        oobtnode['child'] = OOBTNode(name='child')
        self.assertEqual(sorted(root.keys()), ['oobtnode'])
        self.assertEqual(oobtnode.keys(), ['child'])
        self.assertEqual(oobtnode.values(), [oobtnode['child']])
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: child\n'))
        self.checkOutput(
            """\
        OOBTodict([('child', <OOBTNode object 'child' at ...>)])
        """, repr(oobtnode.storage))

        # Reopen database connection and check again
        self.close()
        root = self.open()
        self.assertEqual(sorted(root.keys()), ['oobtnode'])
        oobtnode = root['oobtnode']
        self.assertEqual(oobtnode.keys(), ['child'])
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: child\n'))
        self.assertTrue(oobtnode['child'].__parent__ is oobtnode)

        # Delete child node
        del oobtnode['child']
        transaction.commit()
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'))

        # Check node attributes
        self.assertTrue(isinstance(oobtnode.attrs, OOBTNodeAttributes))
        self.assertEqual(oobtnode.attrs.name, '_attrs')
        oobtnode.attrs['foo'] = 1
        bar = oobtnode.attrs['bar'] = OOBTNode()
        self.assertEqual(oobtnode.attrs.values(), [1, bar])

        # Check attribute access for node attributes
        oobtnode.attribute_access_for_attrs = True
        self.assertEqual(oobtnode.attrs.foo, 1)

        # Check whether flag has been persisted
        self.close()
        root = self.open()
        oobtnode = root['oobtnode']
        self.assertEqual(oobtnode.attrs.foo, 1)
        self.assertEqual(oobtnode.attrs.bar, oobtnode.attrs['bar'])
        oobtnode.attrs.foo = 2
        self.assertEqual(oobtnode.attrs.foo, 2)
        oobtnode.attribute_access_for_attrs = False

        # Check attrs storage
        self.checkOutput(
            """\
        OOBTodict([('foo', 2), ('bar', <OOBTNode object 'bar' at ...>)])
        """, repr(oobtnode.attrs.storage))
        self.checkOutput(
            """\
        OOBTodict([('foo', 2), ('bar', <OOBTNode object 'bar' at ...>)])
        """, repr(oobtnode.attrs._storage))
        self.assertTrue(oobtnode.attrs.storage is oobtnode.attrs._storage)
        self.close()
        root = self.open()
        oobtnode = root['oobtnode']
        oobtnode.attribute_access_for_attrs = False
        self.checkOutput(
            """\
        OOBTodict([('foo', 2), ('bar', <OOBTNode object 'bar' at ...>)])
        """, repr(oobtnode.attrs.storage))

        # Check internal datastructure of attrs
        storage = oobtnode.attrs.storage
        dict_ = storage._dict_impl()
        self.assertTrue(dict_ is OOBTree)
        self.assertEqual(sorted(dict_.keys(storage)),
                         ['____lh', '____lt', 'bar', 'foo'])

        # values ``foo`` and ``bar`` are list tail and list head values
        self.assertEqual(dict_.__getitem__(storage, '____lh'), 'foo')
        self.assertEqual(dict_.__getitem__(storage, '____lt'), 'bar')
        attrs = oobtnode.attrs
        self.assertEqual(dict_.__getitem__(storage, 'bar'),
                         ['foo', attrs['bar'], _nil])
        self.assertEqual(dict_.__getitem__(storage, 'foo'), [_nil, 2, 'bar'])
        self.assertEqual(storage.lt, 'bar')
        self.assertEqual(storage.lh, 'foo')

        # Add attribute, reopen database connection and check again
        oobtnode.attrs['baz'] = 'some added value'
        self.close()
        root = self.open()
        oobtnode = root['oobtnode']
        storage = oobtnode.attrs.storage
        dict_ = storage._dict_impl()
        self.assertEqual(dict_.__getitem__(storage, '____lh'), 'foo')
        self.assertEqual(dict_.__getitem__(storage, '____lt'), 'baz')
        attrs = oobtnode.attrs
        self.assertEqual(dict_.__getitem__(storage, 'bar'),
                         ['foo', attrs['bar'], 'baz'])
        self.assertEqual(dict_.__getitem__(storage, 'baz'),
                         ['bar', 'some added value', _nil])
        self.assertEqual(dict_.__getitem__(storage, 'foo'), [_nil, 2, 'bar'])
        self.assertEqual(storage.lt, 'baz')
        self.assertEqual(storage.lh, 'foo')

        # Test copy and detach
        oobtnode['c1'] = OOBTNode()
        oobtnode['c2'] = OOBTNode()
        oobtnode['c3'] = OOBTNode()
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'))

        # Detach c1
        c1 = oobtnode.detach('c1')
        self.assertTrue(isinstance(c1, OOBTNode))
        self.assertEqual(c1.name, 'c1')
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'))

        # Add c1 as child to c2
        oobtnode['c2'][c1.name] = c1
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'))

        # Reopen database connection and check again
        self.close()
        root = self.open()
        oobtnode = root['oobtnode']
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'))

        # Copy c1
        c1_copy = oobtnode['c2']['c1'].copy()
        self.assertFalse(c1_copy is oobtnode['c2']['c1'])
        oobtnode['c1'] = c1_copy
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'))
        oobtnode['c4'] = oobtnode['c2'].copy()
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c4\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'))
        self.assertFalse(oobtnode['c2']['c1'] is oobtnode['c4']['c1'])
        self.assertFalse(
            oobtnode['c2']['c1'].attrs is oobtnode['c4']['c1'].attrs)
        transaction.commit()

        # Swap nodes
        oobtnode.swap(oobtnode['c1'], oobtnode['c3'])
        oobtnode.swap(oobtnode['c1'], oobtnode['c2'])
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c4\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'))
        st = oobtnode.storage
        self.assertIsInstance(dict_.__getitem__(st, 'c1'), PersistentList)
        self.assertIsInstance(dict_.__getitem__(st, 'c2'), PersistentList)
        self.assertIsInstance(dict_.__getitem__(st, 'c3'), PersistentList)

        # Calling nodes does nothing, persisting is left to transaction
        # mechanism
        oobtnode()

        # Fill root with some OOBTNodes and check memory usage
        old_size = self.storage.getSize()
        root['large'] = OOBTNode()
        for i in range(1000):
            root['large'][str(i)] = OOBTNode()
        self.assertEqual(len(root['large']), 1000)
        transaction.commit()
        new_size = self.storage.getSize()
        self.assertTrue((new_size - old_size) / 1000 <= 914)
        self.close()
예제 #4
0
 def create_booking(self, order, cart_data, uid, count, comment):
     brain = get_catalog_brain(self.context, uid)
     # brain could be None if uid for item in cookie which no longer exists.
     if not brain:
         return
     buyable = brain.getObject()
     item_state = get_item_state(buyable, self.request)
     if not item_state.validate_count(count):
         msg = u'Item no longer available {0}'.format(buyable.id)
         logger.warning(msg)
         raise CheckoutError(msg)
     item_stock = get_item_stock(buyable)
     # stock not applied, state new
     if item_stock is None:
         available = None
         state = ifaces.STATE_NEW
     # calculate state from stock
     else:
         if item_stock.available is not None:
             item_stock.available -= float(count)
         available = item_stock.available
         state = ifaces.STATE_NEW if available is None or available >= 0.0\
             else ifaces.STATE_RESERVED
     item_data = get_item_data_provider(buyable)
     vendor = acquire_vendor_or_shop_root(buyable)
     booking = OOBTNode()
     booking.attrs['email'] = order.attrs['personal_data.email']
     booking.attrs['uid'] = uuid.uuid4()
     booking.attrs['buyable_uid'] = uid
     booking.attrs['buyable_count'] = count
     booking.attrs['buyable_comment'] = comment
     booking.attrs['order_uid'] = order.attrs['uid']
     booking.attrs['vendor_uid'] = uuid.UUID(IUUID(vendor))
     booking.attrs['creator'] = order.attrs['creator']
     booking.attrs['created'] = order.attrs['created']
     booking.attrs['exported'] = False
     booking.attrs['title'] = brain and brain.Title or 'unknown'
     booking.attrs['net'] = item_data.net
     booking.attrs['vat'] = item_data.vat
     # XXX: in order to be able to reliably modify bookings, item discount
     #      rules for this booking must be stored instead of the actual
     #      calculated discount.
     booking.attrs['discount_net'] = item_data.discount_net(count)
     booking.attrs['currency'] = cart_data.currency
     booking.attrs['quantity_unit'] = item_data.quantity_unit
     booking.attrs['remaining_stock_available'] = available
     booking.attrs['state'] = state
     booking.attrs['salaried'] = ifaces.SALARIED_NO
     booking.attrs['tid'] = 'none'
     shipping_info = queryAdapter(buyable, IShippingItem)
     if shipping_info:
         booking.attrs['shippable'] = shipping_info.shippable
     else:
         booking.attrs['shippable'] = False
     trading_info = queryAdapter(buyable, ifaces.ITrading)
     if trading_info:
         booking.attrs['item_number'] = trading_info.item_number
         booking.attrs['gtin'] = trading_info.gtin
     else:
         booking.attrs['item_number'] = None
         booking.attrs['gtin'] = None
     return booking
예제 #5
0
 def create_booking(self, order, cart_data, uid, count, comment):
     brain = get_catalog_brain(self.context, uid)
     # brain could be None if uid for item in cookie which no longer exists.
     if not brain:
         return
     buyable = brain.getObject()
     item_state = get_item_state(buyable, self.request)
     if not item_state.validate_count(item_state.aggregated_count):
         raise CheckoutError(u'Item no longer available')
     item_stock = get_item_stock(buyable)
     if item_stock.available is not None:
         item_stock.available -= float(count)
     available = item_stock.available
     state = (available is None or available >= 0) and ifaces.STATE_NEW\
         or ifaces.STATE_RESERVED
     item_data = get_item_data_provider(buyable)
     vendor = acquire_vendor_or_shop_root(buyable)
     booking = OOBTNode()
     booking.attrs['uid'] = uuid.uuid4()
     booking.attrs['buyable_uid'] = uid
     booking.attrs['buyable_count'] = count
     booking.attrs['buyable_comment'] = comment
     booking.attrs['order_uid'] = order.attrs['uid']
     booking.attrs['vendor_uid'] = uuid.UUID(IUUID(vendor))
     booking.attrs['creator'] = order.attrs['creator']
     booking.attrs['created'] = order.attrs['created']
     booking.attrs['exported'] = False
     booking.attrs['title'] = brain and brain.Title or 'unknown'
     booking.attrs['net'] = item_data.net
     booking.attrs['vat'] = item_data.vat
     booking.attrs['discount_net'] = item_data.discount_net(count)
     booking.attrs['currency'] = cart_data.currency
     booking.attrs['quantity_unit'] = item_data.quantity_unit
     booking.attrs['remaining_stock_available'] = available
     booking.attrs['state'] = state
     booking.attrs['salaried'] = ifaces.SALARIED_NO
     booking.attrs['tid'] = 'none'
     return booking
예제 #6
0
 def __init__(self, name=None, parent=None):
     OOBTNode.__init__(self, name=name, parent=parent)
     self.attrs['title'] = 'foo'
예제 #7
0
 def testSetUp(self):
     self['storage'] = OOBTNode()
예제 #8
0
 def __init__(self, name=None, parent=None):
     OOBTNode.__init__(self, name=name, parent=parent)
     self.attrs["uid"] = uuid.uuid4()
     self.attrs["title"] = "foo"
     self.state = "state_1"
예제 #9
0
 def create_booking(self, order, cart_data, uid, count, comment):
     brain = get_catalog_brain(self.context, uid)
     # brain could be None if uid for item in cookie which no longer exists.
     if not brain:
         return
     buyable = brain.getObject()
     item_state = get_item_state(buyable, self.request)
     if not item_state.validate_count(count):
         msg = u'Item no longer available {0}'.format(buyable.id)
         logger.warning(msg)
         raise CheckoutError(msg)
     item_stock = get_item_stock(buyable)
     # stock not applied, state new
     if item_stock is None:
         available = None
         state = ifaces.STATE_NEW
     # calculate state from stock
     else:
         if item_stock.available is not None:
             item_stock.available -= float(count)
         available = item_stock.available
         state = ifaces.STATE_NEW if available is None or available >= 0.0\
             else ifaces.STATE_RESERVED
     item_data = get_item_data_provider(buyable)
     vendor = acquire_vendor_or_shop_root(buyable)
     booking = OOBTNode()
     booking.attrs['email'] = order.attrs['personal_data.email']
     booking.attrs['uid'] = uuid.uuid4()
     booking.attrs['buyable_uid'] = uid
     booking.attrs['buyable_count'] = count
     booking.attrs['buyable_comment'] = comment
     booking.attrs['order_uid'] = order.attrs['uid']
     booking.attrs['vendor_uid'] = uuid.UUID(IUUID(vendor))
     booking.attrs['creator'] = order.attrs['creator']
     booking.attrs['created'] = order.attrs['created']
     booking.attrs['exported'] = False
     booking.attrs['title'] = brain and brain.Title or 'unknown'
     booking.attrs['net'] = item_data.net
     booking.attrs['vat'] = item_data.vat
     booking.attrs['discount_net'] = item_data.discount_net(count)
     booking.attrs['currency'] = cart_data.currency
     booking.attrs['quantity_unit'] = item_data.quantity_unit
     booking.attrs['remaining_stock_available'] = available
     booking.attrs['state'] = state
     booking.attrs['salaried'] = ifaces.SALARIED_NO
     booking.attrs['tid'] = 'none'
     shipping_info = queryAdapter(buyable, IShippingItem)
     if shipping_info:
         booking.attrs['shippable'] = shipping_info.shippable
     else:
         booking.attrs['shippable'] = False
     trading_info = queryAdapter(buyable, ifaces.ITrading)
     if trading_info:
         booking.attrs['item_number'] = trading_info.item_number
         booking.attrs['gtin'] = trading_info.gtin
     else:
         booking.attrs['item_number'] = None
         booking.attrs['gtin'] = None
     return booking
예제 #10
0
 def test_OOBTNode(self):
     # Based on OOBTree as storage
     oobtnode = OOBTNode('oobtnode')
     # Interface check
     self.assertTrue(IZODBNode.providedBy(oobtnode))
     # Storage check
     self.assertTrue(isinstance(oobtnode.storage, OOBTodict))
     self.assertTrue(isinstance(oobtnode._storage, OOBTodict))
     # Structure check
     root = self.open()
     root[oobtnode.__name__] = oobtnode
     oobtnode['child'] = OOBTNode('child')
     self.assertEqual(sorted(root.keys()), ['oobtnode'])
     self.assertEqual(oobtnode.keys(), ['child'])
     self.assertEqual(oobtnode.values(), [oobtnode['child']])
     self.assertEqual(oobtnode.treerepr(), (
         '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: child\n'
     ))
     self.check_output("""\
     OOBTodict([('child', <OOBTNode object 'child' at ...>)])
     """, repr(oobtnode.storage))
     # Reopen database connection and check again
     self.close()
     root = self.open()
     self.assertEqual(sorted(root.keys()), ['oobtnode'])
     oobtnode = root['oobtnode']
     self.assertEqual(oobtnode.keys(), ['child'])
     self.assertEqual(oobtnode.treerepr(), (
         '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: child\n'
     ))
     self.assertTrue(oobtnode['child'].__parent__ is oobtnode)
     # Delete child node
     del oobtnode['child']
     transaction.commit()
     self.assertEqual(oobtnode.treerepr(), (
         '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
     ))
     # Check node attributes
     self.assertTrue(isinstance(oobtnode.attrs, OOBTNodeAttributes))
     self.assertEqual(oobtnode.attrs.name, '_attrs')
     oobtnode.attrs['foo'] = 1
     bar = oobtnode.attrs['bar'] = OOBTNode()
     self.assertEqual(oobtnode.attrs.values(), [1, bar])
     # Check attribute access for node attributes
     oobtnode.attribute_access_for_attrs = True
     self.assertEqual(oobtnode.attrs.foo, 1)
     # Check whether flag has been persisted
     self.close()
     root = self.open()
     oobtnode = root['oobtnode']
     self.assertEqual(oobtnode.attrs.foo, 1)
     self.assertEqual(oobtnode.attrs.bar, oobtnode.attrs['bar'])
     oobtnode.attrs.foo = 2
     self.assertEqual(oobtnode.attrs.foo, 2)
     oobtnode.attribute_access_for_attrs = False
     # Check attrs storage
     self.check_output("""\
     OOBTodict([('foo', 2), ('bar', <OOBTNode object 'bar' at ...>)])
     """, repr(oobtnode.attrs.storage))
     self.check_output("""\
     OOBTodict([('foo', 2), ('bar', <OOBTNode object 'bar' at ...>)])
     """, repr(oobtnode.attrs._storage))
     self.assertTrue(oobtnode.attrs.storage is oobtnode.attrs._storage)
     self.close()
     root = self.open()
     oobtnode = root['oobtnode']
     oobtnode.attribute_access_for_attrs = False
     self.check_output("""\
     OOBTodict([('foo', 2), ('bar', <OOBTNode object 'bar' at ...>)])
     """, repr(oobtnode.attrs.storage))
     # Check internal datastructure of attrs
     storage = oobtnode.attrs.storage
     cls = storage._dict_impl()
     self.assertTrue(cls is OOBTree)
     self.assertEqual(
         sorted(cls.keys(storage)),
         ['____lh', '____lt', 'bar', 'foo']
     )
     # values ``foo`` and ``bar`` are list tail and list head values
     self.assertEqual(cls.__getitem__(storage, '____lh'), 'foo')
     self.assertEqual(cls.__getitem__(storage, '____lt'), 'bar')
     attrs = oobtnode.attrs
     self.assertEqual(
         cls.__getitem__(storage, 'bar'),
         ['foo', attrs['bar'], _nil]
     )
     self.assertEqual(
         cls.__getitem__(storage, 'foo'),
         [_nil, 2, 'bar']
     )
     self.assertEqual(storage.lt, 'bar')
     self.assertEqual(storage.lh, 'foo')
     # Add attribute, reopen database connection and check again
     oobtnode.attrs['baz'] = 'some added value'
     self.close()
     root = self.open()
     oobtnode = root['oobtnode']
     storage = oobtnode.attrs.storage
     cls = storage._dict_impl()
     self.assertEqual(cls.__getitem__(storage, '____lh'), 'foo')
     self.assertEqual(cls.__getitem__(storage, '____lt'), 'baz')
     attrs = oobtnode.attrs
     self.assertEqual(
         cls.__getitem__(storage, 'bar'),
         ['foo', attrs['bar'], 'baz']
     )
     self.assertEqual(
         cls.__getitem__(storage, 'baz'),
         ['bar', 'some added value', _nil]
     )
     self.assertEqual(
         cls.__getitem__(storage, 'foo'),
         [_nil, 2, 'bar']
     )
     self.assertEqual(storage.lt, 'baz')
     self.assertEqual(storage.lh, 'foo')
     # Test copy and detach
     oobtnode['c1'] = OOBTNode()
     oobtnode['c2'] = OOBTNode()
     oobtnode['c3'] = OOBTNode()
     self.assertEqual(oobtnode.treerepr(), (
         '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
     ))
     # Detach c1
     c1 = oobtnode.detach('c1')
     self.assertTrue(isinstance(c1, OOBTNode))
     self.assertEqual(c1.name, 'c1')
     self.assertEqual(oobtnode.treerepr(), (
         '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
     ))
     # Add c1 as child to c2
     oobtnode['c2'][c1.name] = c1
     self.assertEqual(oobtnode.treerepr(), (
         '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
         '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
     ))
     # Reopen database connection and check again
     self.close()
     root = self.open()
     oobtnode = root['oobtnode']
     self.assertEqual(oobtnode.treerepr(), (
         '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
         '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
     ))
     # Copy c1
     c1_copy = oobtnode['c2']['c1'].copy()
     self.assertFalse(c1_copy is oobtnode['c2']['c1'])
     oobtnode['c1'] = c1_copy
     self.assertEqual(oobtnode.treerepr(), (
         '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
         '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
     ))
     oobtnode['c4'] = oobtnode['c2'].copy()
     self.assertEqual(oobtnode.treerepr(), (
         '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
         '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c4\n'
         '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
     ))
     self.assertFalse(oobtnode['c2']['c1'] is oobtnode['c4']['c1'])
     self.assertFalse(
         oobtnode['c2']['c1'].attrs is oobtnode['c4']['c1'].attrs
     )
     transaction.commit()
     # Swap nodes
     oobtnode.swap(oobtnode['c1'], oobtnode['c3'])
     oobtnode.swap(oobtnode['c1'], oobtnode['c2'])
     self.assertEqual(oobtnode.treerepr(), (
         '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
         '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
         '  <class \'node.ext.zodb.OOBTNode\'>: c4\n'
         '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
     ))
     # Calling nodes does nothing, persisting is left to transaction
     # mechanism
     oobtnode()
     # Fill root with some OOBTNodes and check memory usage
     old_size = self.storage.getSize()
     root['large'] = OOBTNode()
     for i in range(1000):
         root['large'][str(i)] = OOBTNode()
     self.assertEqual(len(root['large']), 1000)
     transaction.commit()
     new_size = self.storage.getSize()
     # ZODB 3 and ZODB 5 return different sizes so check whether lower or
     # equal higher value
     self.assertTrue((new_size - old_size) / 1000 <= 160)
     self.close()