Back to index

plone3  3.1.7
test_update_schema.py
Go to the documentation of this file.
00001 ################################################################################
00002 #
00003 # Copyright (c) 2002-2005, Benjamin Saller <bcsaller@ideasuite.com>, and
00004 #                              the respective authors. All rights reserved.
00005 # For a list of Archetypes contributors see docs/CREDITS.txt.
00006 #
00007 # Redistribution and use in source and binary forms, with or without
00008 # modification, are permitted provided that the following conditions are met:
00009 #
00010 # * Redistributions of source code must retain the above copyright notice, this
00011 #   list of conditions and the following disclaimer.
00012 # * Redistributions in binary form must reproduce the above copyright notice,
00013 #   this list of conditions and the following disclaimer in the documentation
00014 #   and/or other materials provided with the distribution.
00015 # * Neither the name of the author nor the names of its contributors may be used
00016 #   to endorse or promote products derived from this software without specific
00017 #   prior written permission.
00018 #
00019 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00020 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00021 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00022 # FOR A PARTICULAR PURPOSE.
00023 #
00024 ################################################################################
00025 
00026 import os
00027 import sys
00028 
00029 from ZPublisher.HTTPRequest import HTTPRequest
00030 from Testing import ZopeTestCase
00031 
00032 from Products.Archetypes.tests.atsitetestcase import ATSiteTestCase
00033 from Products.Archetypes.tests.utils import makeContent
00034 from Products.Archetypes.tests.utils import mkDummyInContext
00035 from Products.Archetypes.atapi import *
00036 
00037 import shutil
00038 
00039 from Products.CMFCore.utils import getToolByName
00040 from Products.Archetypes.ArchetypeTool import registerType
00041 
00042 textfield1 = TextField('TEXTFIELD1', required=True, default='A')
00043 
00044 textfield1b = TextField('TEXTFIELD1', required=False, default='A')
00045 
00046 textfield2 = TextField('TEXTFIELD2', default='B')
00047 
00048 schema1 = BaseSchema + Schema((
00049         textfield1,
00050         ))
00051 
00052 schema2 = BaseSchema + Schema((
00053         textfield1b,
00054         textfield2,
00055         ))
00056 
00057 
00058 class Dummy1(BaseContent):
00059     pass
00060 
00061 
00062 class Dummy2(BaseContent):
00063     pass
00064 
00065 
00066 class TestUpdateSchema(ZopeTestCase.Sandboxed, ATSiteTestCase):
00067 
00068     def afterSetUp(self):
00069         ATSiteTestCase.afterSetUp(self)
00070         self.attool = self.portal.archetype_tool
00071         # Calling mkDummyInContext adds content, but also registers
00072         # our classes and adds a copy of the schema.
00073         self._dummy1 = mkDummyInContext(
00074             Dummy1, oid='dummy1', context=self.portal, schema=schema1)
00075         self._dummy2 = mkDummyInContext(
00076             Dummy2, oid='dummy2', context=self.portal, schema=schema2)
00077 
00078     def test_instance_schema_is_harmful(self):
00079         """Show that having a schema in the instance is harmful.
00080 
00081         schema should be a class attribute, not an instance attribute.
00082 
00083         The only thing this really tests is that for AT >= 1.5.2,
00084         having a schema attribute on the instance is bad.  In earlier
00085         ATs this is no problem.  Nothing bad happens due to the
00086         earlier AT code.  But the newer ATs cannot handle older
00087         content that has had a schema update already.
00088 
00089         So: if you copy this test to an earlier Archetypes and it
00090         fails, that is okay really.  But in AT >= 1.5.2 it does *not*
00091         fail and this means that some code needs be added to migrate
00092         old content.
00093         """
00094         dummy = self._dummy1
00095         self.failUnless(dummy._isSchemaCurrent())
00096 
00097         # You can make schema an instance attribute if you want (or if
00098         # you are not careful).
00099         self.failIf('schema' in dummy.__dict__)
00100         dummy.schema = dummy.__class__.schema
00101         self.failUnless('schema' in dummy.__dict__)
00102         # The schema has not *really* changed:
00103         self.failUnless(dummy._isSchemaCurrent())
00104         # But the damage has been done, as we will show soon.
00105 
00106         # We give the class of our content a different schema.
00107         dummy.__class__.schema = schema2.copy()
00108         # Reregister the type.  (Not needed in AT <= 1.5.1)
00109         registerType(Dummy1, 'Archetypes')
00110         # We are not testing the _isSchemaCurrent method here, so we
00111         # can simply cheat to let the object know that its schema is
00112         # not current anymore.
00113         dummy._signature = 'bogus'
00114         self.failIf(dummy._isSchemaCurrent())
00115 
00116         # Our class has a TEXTFIELD2, but our content does not now it
00117         # yet.  It *does* already have the getter for that field.
00118         dummy.getTEXTFIELD2
00119         self.assertRaises(KeyError, dummy.getTEXTFIELD2)
00120 
00121         # No problem, we just need to update the schema of the
00122         # content.  Might as well do that for all objects, as that is
00123         # what the user will do in practice.
00124         dummy._updateSchema()
00125 
00126         # And now we can get our second text field, right?  Wrong.
00127         # Only the getter is there and it does not work.
00128         self.failUnless(hasattr(dummy, 'getTEXTFIELD2'))
00129         # Actually, the next two tests fail for AT <= 1.5.1, which is
00130         # actually good.
00131         self.assertRaises(KeyError, dummy.getTEXTFIELD2)
00132         self.failIf(hasattr(dummy, 'TEXTFIELD2'))
00133 
00134         # And the first field was required in the first schema but not
00135         # in the second.  This does not show yet.
00136         self.failUnless(dummy.getField('TEXTFIELD1').required)
00137 
00138         # This can be fixed by deleting the schema attribute of the
00139         # instance.
00140         del dummy.schema
00141         self.failIf(dummy.getField('TEXTFIELD1').required)
00142 
00143         # At first, direct attribute access for the second field still
00144         # does not work:
00145         self.failIf(hasattr(dummy, 'TEXTFIELD2'))
00146         # But calling the getter works.
00147         self.assertEqual(dummy.getTEXTFIELD2(), 'B')
00148         # And after that call, direct attribute access works too.
00149         self.assertEqual(dummy.TEXTFIELD2(), 'B')
00150         # Note: TEXTFIELD is a BaseUnit, which means you need to call
00151         # it to get its value.
00152         
00153     def test_no_schema_attribute_added(self):
00154         """Does updating the schema mess things up?
00155 
00156         Updating the schema should not add the schema as instance
00157         attribute, unless you *really* know what you are doing.
00158         """
00159         dummy = self._dummy1
00160         dummy._updateSchema()
00161         self.failIf('schema' in dummy.__dict__)
00162 
00163     def test_detect_schema_change(self):
00164         dummy = self._dummy1
00165         self.failUnless(dummy._isSchemaCurrent())
00166         dummy.__class__.schema = schema2.copy()
00167         # Reregister the type.  (Not needed in AT <= 1.5.1)
00168         registerType(Dummy1, 'Archetypes')
00169         self.failIf(dummy._isSchemaCurrent())
00170         dummy._updateSchema()
00171         self.failUnless(dummy._isSchemaCurrent())
00172 
00173     def test_remove_instance_schemas(self):
00174         dummy = self._dummy1
00175         dummy.schema = schema2.copy()
00176         self.failUnless('schema' in dummy.__dict__)
00177         dummy._updateSchema()
00178         self.failUnless('schema' in dummy.__dict__)
00179         dummy._updateSchema(remove_instance_schemas=True)
00180         self.failIf('schema' in dummy.__dict__)
00181 
00182     def test_manage_update_schema(self):
00183         dummy = self._dummy1
00184         dummy.schema = schema2.copy()
00185         self.failUnless('schema' in dummy.__dict__)
00186         self.failIf(dummy._isSchemaCurrent())
00187 
00188         # Now we want to update all schemas, but first archetype_tool
00189         # needs to know that our class needs updating.  The easiest of
00190         # course is to cheat.
00191         self.assertEqual(self.types_to_update(), [])
00192         self.attool._types['Archetypes.Dummy1'] = 'cheat'
00193         self.assertEqual(self.types_to_update(), ['Archetypes.Dummy1'])
00194 
00195         # Now we are ready to call manage_updateSchema
00196         self.attool.manage_updateSchema()
00197         # This will have no effect on the schema attribute:
00198         self.failUnless('schema' in dummy.__dict__)
00199         # It *does* wrongly mark the schema as current.
00200         self.failUnless(dummy._isSchemaCurrent())
00201         # So we cheat again and then it works.
00202         dummy._signature = 'bogus'
00203         self.failIf(dummy._isSchemaCurrent())
00204 
00205         # Let's try again.  But first we cheat again.
00206         self.assertEqual(self.types_to_update(), [])
00207         self.attool._types['Archetypes.Dummy1'] = 'cheat'
00208         self.assertEqual(self.types_to_update(), ['Archetypes.Dummy1'])
00209 
00210         # We need to call manage_updateSchema with an extra option.
00211         self.attool.manage_updateSchema(remove_instance_schemas=True)
00212         self.failIf('schema' in dummy.__dict__)
00213 
00214     def types_to_update(self):
00215         """Which types have a changed schema?
00216         """
00217         return [ti[0] for ti in self.attool.getChangedSchema() if ti[1]]
00218 
00219 class TestBasicSchemaUpdate(ATSiteTestCase):
00220     """Tests for update schema behavior which depend only on the basic
00221        types, and examine baseline behavior when no real schema changes have
00222        happened."""
00223 
00224     def test_update_preserves_mimetype(self):
00225         self.folder.invokeFactory('DDocument', 'mydoc', title="My Doc")
00226         doc = self.folder.mydoc
00227         doc.setBody("""
00228 An rst Document
00229 ===============
00230 
00231 * Which
00232 
00233   * has
00234 
00235   * some
00236 
00237 * bullet::
00238 
00239   points.
00240 
00241 * for testing""",  mimetype="text/restructured")
00242         doc.reindexObject()
00243         mimetype = doc.getField('body').getContentType(doc)
00244         self.assertEqual(mimetype, 'text/x-rst')
00245 
00246         # update schema for all DDocuments and check if our type is preserved
00247         request = HTTPRequest(sys.stdin,
00248                               {'SERVER_NAME':'test', 'SERVER_PORT': '8080'},
00249                               {})
00250         request.form['Archetypes.DDocument'] = True
00251         request.form['update_all'] = True
00252         self.portal.archetype_tool.manage_updateSchema(REQUEST=request)
00253         doc = self.folder.mydoc
00254         mimetype = doc.getField('body').getContentType(doc)
00255         self.assertEqual(mimetype, 'text/x-rst')
00256 
00257 
00258 
00259 def test_suite():
00260     from unittest import TestSuite, makeSuite
00261     suite = TestSuite()
00262     suite.addTest(makeSuite(TestUpdateSchema))
00263     suite.addTest(makeSuite(TestBasicSchemaUpdate))
00264     return suite