Skip to content

Commit

Permalink
support identifying content types by a name (with implicit creation o…
Browse files Browse the repository at this point in the history
…f an interface)
  • Loading branch information
davisagli committed Apr 17, 2012
1 parent baee4ce commit 030166d
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 16 deletions.
20 changes: 19 additions & 1 deletion docs/content.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ that generates content must have these properties:
introspected by various Substance D UI elements such as "add forms", and
queries by the management interface for the icon name of a resource. The
type is defined as a class that inherits from the
:class:`substanced.content.Type` class.
:class:`substanced.content.Type` class, or as a string.

Here's an example which defines a content resource factory as a class:

Expand All @@ -61,6 +61,24 @@ Here's an example which defines a content resource factory as a class:
self.title = title
self.body = body
Here's an example of defining a content resource factory with the type given
as a string instead of a Type instance:

.. code-block:: python
# in a module named blog.resources
from persistent import Persistent
from substanced.content import (
content,
)
@content('BlogEntryType')
class BlogEntry(Persistent):
def __init__(self, title, body):
self.title = title
self.body = body
Here's an example of defining a content resource factory using a function
instead:

Expand Down
43 changes: 29 additions & 14 deletions substanced/content/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Interface,
implementer,
)
from zope.interface.interface import InterfaceClass

import venusian

Expand All @@ -33,17 +34,22 @@ def __init__(self, category_iface=IContent):
def add(self, content_iface, factory, **meta):
addbase(content_iface, self.category_iface)
self.factories[content_iface] = factory
if 'name' in meta:
name = meta.pop('name')
self.factories[name] = factory
for k, v in meta.items():
content_iface.setTaggedValue(k, v)
def create(self, content_iface, *arg, **kw):
return self.factories[content_iface](*arg, **kw)

def create(self, content_type, *arg, **kw):
return self.factories[content_type](*arg, **kw)

def all(self, context=_marker, **meta):
if context is _marker:
candidates = self.factories.keys()
candidates = [i for i in self.factories.keys()
if IInterface.providedBy(i)]
else:
candidates = [i for i in providedBy(context) if i in self.factories]
candidates = [i for i in providedBy(context) if i in self.factories
and IInterface.providedBy(i)]
if not meta:
return candidates
matches_meta = []
Expand Down Expand Up @@ -76,20 +82,21 @@ class content(object):
""" Use as a decorator for a content factory (usually a class). Accepts
a content interface and a set of meta keywords."""
venusian = venusian
def __init__(self, content_iface, **meta):
self.content_iface = content_iface
def __init__(self, content_type, **meta):
self.content_type = content_type
self.meta = meta

def __call__(self, wrapped):
implementer(self.content_iface)(wrapped)
if IInterface.providedBy(self.content_type):
implementer(self.content_type)(wrapped)
def callback(context, name, ob):
config = context.config.with_package(info.module)
config.add_content_type(self.content_iface, wrapped, **self.meta)
config.add_content_type(self.content_type, wrapped, **self.meta)
info = self.venusian.attach(wrapped, callback, category='substanced')
self.meta['_info'] = info.codeinfo # fbo "action_method"
return wrapped

def add_content_type(config, content_iface, factory, **meta):
def add_content_type(config, content_type, factory, **meta):
"""
Configurator method which adds a content type. Call via
``config.add_content_type`` during Pyramid configuration phase.
Expand All @@ -98,11 +105,17 @@ def add_content_type(config, content_iface, factory, **meta):
``**meta`` is an arbitrary set of keywords associated with the content
type in the content registry.
"""
if not IInterface.providedBy(content_iface):
if IInterface.providedBy(content_type):
content_iface = content_type
elif isinstance(content_type, basestring):
meta['name'] = content_type
content_iface = InterfaceClass(content_type, ())
else:
raise ConfigurationError(
'The provided "content_iface" argument (%r) is not an '
'interface object (it does not inherit from '
'zope.interface.Interface)' % type)
'The provided "content_type" argument (%r) is not a '
'string or an interface (it does not inherit from '
'zope.interface.Interface)' % content_type
)

if not content_iface.implementedBy(factory):
# was not called by decorator
Expand All @@ -114,6 +127,8 @@ def add_content_type(config, content_iface, factory, **meta):
def register_factory():
config.registry.content.add(content_iface, factory, **meta)

if 'IFolder' in repr(content_iface):
import pdb; pdb.set_trace()
discrim = ('sd-content-type', content_iface)

intr = config.introspectable(
Expand Down
47 changes: 46 additions & 1 deletion substanced/content/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,24 @@ class IFoo(Interface):
inst.add(IFoo, True, icon='fred')
self.assertEqual(inst.factories[IFoo], True)
self.assertEqual(IFoo.getTaggedValue('icon'), 'fred')


def test_add_with_name(self):
inst = self._makeOne(ICategory)
class IFoo(Interface):
pass
inst.add(IFoo, True, name='bar')
self.assertEqual(inst.factories['bar'], True)

def test_create(self):
inst = self._makeOne(ICategory)
inst.factories[IDummy] = lambda a: a
self.assertEqual(inst.create(IDummy, 'a'), 'a')

def test_create_with_name(self):
inst = self._makeOne(ICategory)
inst.factories['dummy'] = lambda a: a
self.assertEqual(inst.create('dummy', 'a'), 'a')

def test_all_no_context(self):
inst = self._makeOne(ICategory)
inst.factories[IDummy] = True
Expand Down Expand Up @@ -156,6 +168,17 @@ def test_decorates_class(self):
ct = config.content_types
self.assertEqual(len(ct), 1)

def test_decorates_class_with_name(self):
decorator = self._makeOne('foo')
venusian = DummyVenusian()
decorator.venusian = venusian
decorator.venusian.info.scope = 'class'
wrapped = decorator(Special)
self.assertTrue(wrapped is Special)
config = call_venusian(venusian)
ct = config.content_types
self.assertEqual(len(ct), 1)

def test_decorates_function(self):
decorator = self._makeOne(ISpecial)
venusian = DummyVenusian()
Expand Down Expand Up @@ -223,6 +246,28 @@ class IFoo(Interface):
content = config.registry.content.added[0][0][1]()
self.assertEqual(content.__class__, Foo)
self.assertTrue(IFoo.providedBy(content))

def test_success_class_with_name(self):
config = DummyConfig()
config.registry.content = DummyContentRegistry()
class Foo(object):
pass
self._callFUT(config, 'foo', Foo, category=ICategory)
self.assertEqual(len(config.actions), 1)
self.assertEqual('sd-content-type', config.actions[0][0][0][0])
self.assertEqual('<InterfaceClass substanced.content.foo>',
repr(config.actions[0][0][0][1]))
config.actions[0][1]['callable']()
self.assertEqual(
config.registry.content.added[0][1]['category'], ICategory)
content_iface = config.registry.content.added[0][0][0]
self.assertEqual(
repr(content_iface),
'<InterfaceClass substanced.content.foo>')
content = config.registry.content.added[0][0][1]()
self.assertEqual(content.__class__, Foo)
self.assertTrue(content_iface.providedBy(content))


class Test_provides_factory(unittest.TestCase):
def _callFUT(self, factory, content_iface):
Expand Down

0 comments on commit 030166d

Please sign in to comment.