= ZCML Directives = We have a bunch of custom zcml directives in Launchpad. == Canonical URLs == See canonical_url.txt for information and tests of the browser:url directive. == A zcml context for zcml directive unittests == This is a context that collects actions and has a __repr__ for use in tests that shows a pretty-printed representation of the actions. We have to add the 'kw' arg for the EditFormDirective tests, but it isn't important, so we just discard it. >>> import re, pprint >>> atre = re.compile(' at [0-9a-fA-Fx]+') >>> class Context(object): ... actions = () ... def action(self, discriminator, callable, args, kw=None): ... self.actions += ((discriminator, callable, args), ) ... def __repr__(self): ... stream = StringIO() ... pprinter = pprint.PrettyPrinter(stream=stream, width=60) ... pprinter.pprint(self.actions) ... r = stream.getvalue() ... return (''.join(atre.split(r))).strip() The code to this is actually repeated all through the Zope 3 unit tests for zcml directives. :-( == Setting up some interfaces and objects to test with == We'll put an interface in lp.testing.IFoo, and set the default view for the IFooLayer layer. >>> from zope.component import queryMultiAdapter >>> import lp.testing >>> from zope.interface import Interface, implements >>> class IFoo(Interface): ... pass >>> class IFooLayer(Interface): ... pass >>> lp.testing.IFoo = IFoo >>> lp.testing.IFooLayer = IFooLayer >>> class FooObject: ... implements(IFoo) >>> fooobject = FooObject() >>> class Request: ... implements(IFooLayer) >>> request = Request() >>> import types >>> class FooView: ... """classic class that is used for a view on an IFoo""" ... __metaclass__ = types.ClassType ... ... def __call__(self): ... return "FooView was called" >>> lp.testing.FooView = FooView == Overriding the browser:page directive == We override browser:page to allow us to specify the facet that the page is associated with. First, a unit test of the overridden directive. >>> from lp.services.webapp.metazcml import page >>> context = Context() >>> context.info = "INFO" Name some variables to mirror the names used as arguments. >>> name = "NAME" >>> permission = "PERMISSION" >>> for_ = "FOR_" >>> facet = "FACET" >>> page(context, name, permission, for_, facet=facet) # This is a pointless doctest - it is totally non obvious what the result # is supposed to mean, or what is being tested, so when it breaks we have # no idea if the test is broken or the code being tested is broken. # -- StuartBishop 20060411 # # >>> context # ((None, # , # ('', 'FOR_')), # (('view', # 'FOR_', # 'NAME', # , # ), # , # ('Adapters', # 'register', # ('FOR_', # ), # , # 'NAME', # , # 'INFO'))) Look for the SimpleLaunchpadViewClass in the data structure above, and check its __launchpad_facetname__ attribute. >>> from zope.security.proxy import isinstance >>> def get_simplelaunchpadviewclass(actions_tuple): ... for o in actions_tuple: ... if isinstance(o, tuple): ... o = get_simplelaunchpadviewclass(o) ... if (hasattr(o, '__name__') ... and o.__name__ == 'SimpleLaunchpadViewClass'): ... return o ... return None >>> cls = get_simplelaunchpadviewclass(context.actions) >>> cls >>> cls.__launchpad_facetname__ 'FACET' Next, a functional/integration test of the overridden directive. >>> print queryMultiAdapter((fooobject, request), name='+whatever') None >>> print queryMultiAdapter((fooobject, request), name='+mandrill') None >>> from zope.configuration import xmlconfig >>> zcmlcontext = xmlconfig.string(""" ... ... ... ... ... ... """) >>> whatever_view = queryMultiAdapter( ... (fooobject, request), name='+whatever') >>> print whatever_view.__class__.__name__ FooView >>> print whatever_view.__launchpad_facetname__ the_evil_facet >>> mandrill_view = queryMultiAdapter( ... (fooobject, request), name='+mandrill') >>> print mandrill_view.__class__.__name__ SimpleViewClass from ...base-layout.pt >>> print mandrill_view.__launchpad_facetname__ another-mister-lizard == Overriding the browser:pages directive == We override browser:pages to allow us to specify the facet that each page is associated with. First, a unit test of the overridden directive. >>> from lp.services.webapp.metazcml import pages >>> context = Context() >>> context.info = "INFO" Name some variables to mirror the names used as arguments. >>> for_ = "FOR_" >>> permission = "PERMISSION" >>> name = "NAME" >>> facet = "FACET" The facet specified for the outer pages element will be used only when a facet is not specified for the inner page. >>> P = pages(context, permission, for_, facet="OUTERFACET") >>> P.page(context, name, facet=facet) >>> P.page(context, "OTHER NAME") Look for the SimpleLaunchpadViewClass in the data structure above, and check its __launchpad_facetname__ attribute. >>> cls = get_simplelaunchpadviewclass(context.actions) >>> cls >>> cls.__launchpad_facetname__ 'FACET' >>> cls2 = context.actions[3][2][1] >>> cls2 >>> cls2.__launchpad_facetname__ 'OUTERFACET' Next, a functional/integration test of the overridden directive. >>> print queryMultiAdapter((fooobject, request), name='+whatever2') None >>> zcmlcontext = xmlconfig.string(""" ... ... ... ... ... ... ... ... """) >>> whatever2_view = queryMultiAdapter( ... (fooobject, request), name='+whatever2') >>> print whatever2_view.__class__.__name__ FooView >>> print whatever2_view.__launchpad_facetname__ another_evil_facet >>> whatever3_view = queryMultiAdapter( ... (fooobject, request), name='+whatever3') >>> print whatever3_view.__class__.__name__ FooView >>> print whatever3_view.__launchpad_facetname__ outerspace == Overriding the browser:editform directive == We override browser:editform to allow us to specify the facet that the form is associated with. First, a unit test of the overridden directive. >>> from lp.services.webapp.metazcml import EditFormDirective >>> context = Context() >>> context.info = "INFO" Name some variables to mirror the names used as arguments. >>> for_ = "FOR_" >>> name = "NAME" >>> facet = "FACET" >>> schema = Interface >>> EF = EditFormDirective(context, for_=for_, permission=permission, ... schema=schema, name=name, facet=facet) >>> EF() # Another pointless test -- StuartBishop 20060411 # >>> context # ((('view', # 'FOR_', # 'NAME', # , # ), # , # ('NAME', # , # None, # 'PERMISSION', # , # 'edit.pt', # 'edit.pt', # (, # ), # 'FOR_', # [])),) Look for the SimpleLaunchpadViewClass in the data structure above, and check its __launchpad_facetname__ attribute. >>> cls = get_simplelaunchpadviewclass(context.actions) >>> print cls.__name__ SimpleLaunchpadViewClass >>> print cls.__launchpad_facetname__ FACET Next, a functional/integration test of the overridden directive. >>> print queryMultiAdapter((fooobject, request), name='+someeditform') None >>> zcmlcontext = xmlconfig.string(""" ... ... ... ... ... """) >>> whatever_view = queryMultiAdapter( ... (fooobject, request), name='+someeditform') >>> print whatever_view.__class__.__name__ SimpleViewClass from edit.pt >>> print whatever_view.__launchpad_facetname__ another_mister_lizard == Overriding the browser:addform directive == We override browser:addform to allow us to specify the facet that the form is associated with. First, a unit test of the overridden directive. >>> from lp.services.webapp.metazcml import AddFormDirective >>> context = Context() >>> context.info = "INFO" Name some variables to mirror the names used as arguments. >>> for_ = "FOR_" >>> name = "NAME" >>> facet = "FACET" >>> schema = Interface >>> AF = AddFormDirective(context, for_=for_, permission=permission, ... schema=schema, name=name, facet=facet) >>> AF() # Another pointless test -- StuartBishop 20060411 # >>> context # ((('view', # 'FOR_', # 'NAME', # , # ), # , # ('NAME', # , # None, # 'PERMISSION', # , # 'add.pt', # 'add.pt', # (, # ), # 'FOR_', # [], # None, # None, # None, # None, # [])),) Look for the SimpleLaunchpadViewClass in the data structure above, and check its __launchpad_facetname__ attribute. >>> cls = get_simplelaunchpadviewclass(context.actions) >>> print cls.__name__ SimpleLaunchpadViewClass >>> print cls.__launchpad_facetname__ FACET Next, a functional/integration test of the overridden directive. >>> print queryMultiAdapter((fooobject, request), name='+someaddform') None >>> zcmlcontext = xmlconfig.string(""" ... ... ... ... ... """) >>> whatever_view = queryMultiAdapter( ... (fooobject, request), name='+someaddform') >>> print whatever_view.__class__.__name__ SimpleViewClass from add.pt >>> print whatever_view.__launchpad_facetname__ yet_another_mister_lizard == Overriding the browser:schemadisplay directive == We override browser:schemadisplay to allow us to specify the facet that the form is associated with. First, a unit test of the overridden directive. >>> from lp.services.webapp.metazcml import SchemaDisplayDirective >>> context = Context() >>> context.info = "INFO" Name some variables to mirror the names used as arguments. >>> for_ = "FOR_" >>> name = "NAME" >>> facet = "FACET" >>> schema = Interface >>> SDD = SchemaDisplayDirective(context, for_=for_, permission=permission, ... schema=schema, name=name, facet=facet) >>> SDD() # Pointless test -- StuartBishop 20060411 # >>> context # ((('view', # 'FOR_', # 'NAME', # , # ), # , # ('NAME', # , # None, # 'PERMISSION', # , # 'display.pt', # 'display.pt', # (, # ), # 'FOR_', # [], # None)),) Look for the SimpleLaunchpadViewClass in the data structure above, and check its __launchpad_facetname__ attribute. >>> cls = get_simplelaunchpadviewclass(context.actions) >>> print cls.__name__ SimpleLaunchpadViewClass >>> print cls.__launchpad_facetname__ FACET Next, a functional/integration test of the overridden directive. >>> print queryMultiAdapter( ... (fooobject, request), name='+someschemadisplay') None >>> zcmlcontext = xmlconfig.string(""" ... ... ... ... ... """) >>> schemadisplay_view = queryMultiAdapter( ... (fooobject, request), name='+someschemadisplay') >>> print schemadisplay_view.__class__.__name__ SimpleViewClass from display.pt >>> print schemadisplay_view.__launchpad_facetname__ hurrah_yet_another_mister_lizard == Overriding zope:configure to add a facet attribute == We override the grouping directive zope:configure to add a 'facet' attribute that can be inherited by all of the directives it contains. >>> from lp.services.webapp.metazcml import GroupingFacet >>> context = Context() Name some variables to mirror the names used as arguments. >>> facet = 'whole-file-facet' >>> gc = GroupingFacet(context, facet=facet) >>> gc.facet 'whole-file-facet' Next, a functional/integration test of the overridden directive. >>> print queryMultiAdapter((fooobject, request), name='+impliedfacet') None >>> zcmlcontext = xmlconfig.string(""" ... ... ... ... ... ... ... ... ... ... ... """) >>> impliedfacet_view = queryMultiAdapter( ... (fooobject, request), name='+impliedfacet') >>> print impliedfacet_view.__class__.__name__ FooView >>> print impliedfacet_view.__launchpad_facetname__ whole-facet >>> impliedfacet_add = queryMultiAdapter( ... (fooobject, request), name='+impliedfacet-add') >>> print impliedfacet_add.__class__.__name__ SimpleViewClass from add.pt >>> print impliedfacet_add.__launchpad_facetname__ whole-facet >>> impliedfacet_edit = queryMultiAdapter( ... (fooobject, request), name='+impliedfacet-edit') >>> print impliedfacet_edit.__class__.__name__ SimpleViewClass from edit.pt >>> print impliedfacet_edit.__launchpad_facetname__ whole-facet >>> impliedfacet_schemadisplay = queryMultiAdapter( ... (fooobject, request), name='+impliedfacet-schemadisplay') >>> print impliedfacet_schemadisplay.__class__.__name__ SimpleViewClass from display.pt >>> print impliedfacet_schemadisplay.__launchpad_facetname__ whole-facet == Overriding zope:permission == The permissions used in Launchpad must also specify the level of access they require ('read' or 'write'), so our zope:permission directive will register an ILaunchpadPermission with the given access_level instead of an IPermission. >>> zcmlcontext = xmlconfig.string(""" ... ... ... ... ... """) >>> from lp.services.webapp.metazcml import ILaunchpadPermission >>> from lp.services.webapp.testing import verifyObject >>> permission = getUtility(ILaunchpadPermission, 'foo.bar') >>> verifyObject(ILaunchpadPermission, permission) True >>> permission.access_level u'read' == Cleaning up the interfaces and objects to test with == Clean up the interfaces we created for testing with. >>> del lp.testing.IFoo >>> del lp.testing.IFooLayer >>> del lp.testing.FooView