Skip to content

Commit

Permalink
intermediate step in combining ContentCategory/ContentCategories; all…
Browse files Browse the repository at this point in the history
…ow metadata to be specified in the @content constructor
  • Loading branch information
mcdonc committed Apr 9, 2012
1 parent fa07154 commit 4db65c4
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 71 deletions.
2 changes: 1 addition & 1 deletion substanced/catalog/__init__.py
Expand Up @@ -19,7 +19,7 @@

logger = logging.getLogger(__name__)

@content(ICatalog)
@content(ICatalog, icon='icon-search')
class Catalog(_Catalog):
family = BTrees.family32
def __init__(self, family=None):
Expand Down
73 changes: 45 additions & 28 deletions substanced/content/__init__.py
Expand Up @@ -26,27 +26,42 @@ def __init__(self, category_iface):
self.category_iface = category_iface
self.factories = {}

def add(self, content_iface, factory):
def add(self, content_iface, factory, **meta):
self.factories[content_iface] = 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 provided_by(self, resource):
return self.category_iface.providedBy(resource)

def all(self, context=_marker):
def all(self, context=_marker, **meta):
if context is _marker:
return self.factories.keys()
return [i for i in providedBy(context) if i in self.factories]

def first(self, context):
ifaces = [i for i in providedBy(context) if i in self.factories]
if not ifaces:
raise ValueError('%s is not content' % context)
return ifaces[0]

def get_meta(self, context, name, default=None):
candidates = self.factories.keys()
else:
candidates = [i for i in providedBy(context) if i in self.factories]
if not meta:
return candidates
matches_meta = []
for candidate in candidates:
ok = True
for k, v in meta.items():
if not candidate.queryTaggedValue(k) == v:
ok = False
break
if ok:
matches_meta.append(candidate)
return matches_meta

def first(self, context, **meta):
matching = self.all(context, **meta)
if not matching:
raise ValueError('No match!')
return matching[0]

def metadata(self, context, name, default=None):
content_ifaces = self.all(context)
for iface in content_ifaces:
maybe = iface.queryTaggedValue(name, default)
Expand All @@ -58,14 +73,15 @@ class ContentCategories(object):
def __init__(self):
self.categories = {}

def add(self, content_iface, factory, category_iface=None):
def add(self, content_iface, factory, **meta):
category_iface = meta.get('category')
if category_iface is None:
category_iface = IContent
if category_iface is not None:
addbase(content_iface, category_iface)
category = self.categories.setdefault(category_iface,
ContentCategory(category_iface))
category.add(content_iface, factory)
category.add(content_iface, factory, **meta)

def __getitem__(self, category_iface):
return self.categories[category_iface]
Expand All @@ -76,44 +92,45 @@ def create(self, content_iface, *arg, **kw):
def provided_by(self, resource):
return self.categories[IContent].provided_by(resource)

def all(self, context=_marker):
return self.categories[IContent].all(context)
def all(self, context=_marker, **meta):
return self.categories[IContent].all(context, **meta)

def first(self, context):
return self.categories[IContent].first(context)
def first(self, context, **meta):
return self.categories[IContent].first(context, **meta)

def get_meta(self, context, name, default=None):
return self.categories[IContent].get_meta(context, name, default)
def metadata(self, context, name, default=None):
return self.categories[IContent].metadata(context, name, default)

# venusian decorator that marks a class as a content class
class content(object):
venusian = venusian
def __init__(self, content_iface, category_iface=None):
def __init__(self, content_iface, **meta):
self.content_iface = content_iface
self.category_iface = category_iface
self.meta = meta

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

def add_content_type(config, content_iface, factory, category_iface=None):
def add_content_type(config, content_iface, factory, **meta):
if not IInterface.providedBy(content_iface):
raise ConfigurationError(
'The provided "content_iface" argument (%r) is not an '
'interface object (it does not inherit from '
'zope.interface.Interface' % type)

category_iface = meta.get('category')

if category_iface is not None:

if not IInterface.providedBy(category_iface):
raise ConfigurationError(
'The provided "category_iface" argument (%r) is not an '
'The provided "category" argument (%r) is not an '
'interface object (it does not inherit from '
'zope.interface.Interface' % type)

Expand All @@ -122,7 +139,7 @@ def add_content_type(config, content_iface, factory, category_iface=None):
implementer(content_iface)(factory)

def register_factory():
config.registry.content.add(content_iface, factory, category_iface)
config.registry.content.add(content_iface, factory, **meta)

discrim = ('content-type', content_iface, category_iface)
config.action(discrim, callable=register_factory)
Expand Down
42 changes: 26 additions & 16 deletions substanced/content/tests.py
Expand Up @@ -45,6 +45,14 @@ def test_add(self):
inst.add(IDummy, True)
self.assertEqual(inst.factories[IDummy], True)

def test_add_with_meta(self):
inst = self._makeOne(ICategory)
class IFoo(Interface):
pass
inst.add(IFoo, True, icon='fred')
self.assertEqual(inst.factories[IFoo], True)
self.assertEqual(IFoo.getTaggedValue('icon'), 'fred')

def test_create(self):
inst = self._makeOne(ICategory)
inst.factories[IDummy] = lambda a: a
Expand Down Expand Up @@ -96,23 +104,23 @@ def test_first_noprovides(self):
dummy = Dummy()
self.assertRaises(ValueError, inst.first, dummy)

def test_get_meta(self):
def test_metadata(self):
inst = self._makeOne(ICategory)
inst.factories[IDummy] = True
inst.factories[ICategory] = True
dummy = Dummy()
alsoProvides(dummy, IDummy)
alsoProvides(dummy, ICategory)
self.assertEqual(inst.get_meta(dummy, 'icon'), 'icon-name')
self.assertEqual(inst.metadata(dummy, 'icon'), 'icon-name')

def test_get_meta_notfound(self):
def test_metadata_notfound(self):
inst = self._makeOne(ICategory)
inst.factories[IDummy] = True
inst.factories[ICategory] = True
dummy = Dummy()
alsoProvides(dummy, IDummy)
alsoProvides(dummy, ICategory)
self.assertEqual(inst.get_meta(dummy, 'doesntexist'), None)
self.assertEqual(inst.metadata(dummy, 'doesntexist'), None)

class TestContentCategories(unittest.TestCase):
def _makeOne(self):
Expand All @@ -127,7 +135,6 @@ class Factory(object):
pass
inst.add(IFoo, Factory)
self.assertEqual(inst.categories[IContent].factories[IFoo], Factory)
## self.assertTrue(IFoo.providedBy(Factory()))
self.assertTrue(IContent in IFoo.__iro__)
self.assertTrue(IContent in IFoo.__bases__)

Expand All @@ -137,9 +144,8 @@ class IFoo(Interface):
pass
class Factory(object):
pass
inst.add(IFoo, Factory, ICategory)
inst.add(IFoo, Factory, category=ICategory)
self.assertEqual(inst.categories[ICategory].factories[IFoo], Factory)
## self.assertTrue(IFoo.providedBy(Factory()))
self.assertTrue(ICategory in IFoo.__iro__)
self.assertTrue(ICategory in IFoo.__bases__)

Expand Down Expand Up @@ -179,11 +185,11 @@ def test_all_no_context(self):
inst.categories[IContent] = DummyCategory(None)
self.assertEqual(inst.all(), [])

def test_get_meta(self):
def test_metadata(self):
inst = self._makeOne()
inst.categories[IContent] = DummyCategory(None)
dummy = Dummy()
self.assertEqual(inst.get_meta(dummy, 'abc'), 'abc')
self.assertEqual(inst.metadata(dummy, 'abc'), 'abc')

class Test_content(unittest.TestCase):
def _makeOne(self, iface):
Expand Down Expand Up @@ -222,20 +228,24 @@ def _callFUT(self, *arg, **kw):
def test_content_iface_not_IInterface(self):
from pyramid.exceptions import ConfigurationError
self.assertRaises(
ConfigurationError, self._callFUT, None, object(), None, IDummy)
ConfigurationError,
self._callFUT,
None, object(), None, category=IDummy)

def test_category_iface_not_IInterface(self):
from pyramid.exceptions import ConfigurationError
self.assertRaises(
ConfigurationError, self._callFUT, None, IDummy, None, object())
ConfigurationError,
self._callFUT,
None, IDummy, None, category=object())

def test_success(self):
def factory(): pass
config = DummyConfig()
config.registry.content = DummyContentCategories()
class IFoo(Interface):
pass
self._callFUT(config, IFoo, factory, ICategory)
self._callFUT(config, IFoo, factory, category=ICategory)
self.assertEqual(len(config.actions), 1)
self.assertEqual(
config.actions[0][0],
Expand All @@ -244,14 +254,14 @@ class IFoo(Interface):
config.actions[0][1]['callable']()
self.assertEqual(
config.registry.content.added,
[(IFoo, factory, ICategory)])
[((IFoo, factory), {'category':ICategory})])

class DummyContentCategories(object):
def __init__(self):
self.added = []

def add(self, *arg):
self.added.append(arg)
def add(self, *arg, **meta):
self.added.append((arg, meta))

class DummyCategory(object):
def __init__(self, result):
Expand All @@ -272,7 +282,7 @@ def all(self, context=None):
def first(self, context):
return None

def get_meta(self, context, name, default=None):
def metadata(self, context, name, default=None):
return name

class ICategory(Interface):
Expand Down
5 changes: 1 addition & 4 deletions substanced/folder/__init__.py
Expand Up @@ -4,8 +4,6 @@
from BTrees.OOBTree import OOBTree
from BTrees.Length import Length

from zope.interface import implementer

from ..interfaces import (
IFolder,
marker,
Expand All @@ -23,8 +21,7 @@

from ..service import find_service

@implementer(IFolder)
@content(IFolder)
@content(IFolder, icon='icon-folder-close')
class Folder(Persistent):
""" A folder implementation which acts much like a Python dictionary.
Expand Down
13 changes: 0 additions & 13 deletions substanced/interfaces.py
Expand Up @@ -3,7 +3,6 @@
from zope.interface import (
Interface,
Attribute,
taggedValue,
)

class IContent(Interface):
Expand Down Expand Up @@ -33,7 +32,6 @@ def set_properties(struct):

class IObjectMap(Interface):
""" A map of objects to paths """
taggedValue('icon', 'icon-asterisk')
def objectid_for(obj_or_path_tuple):
""" Return the object id for obj_or_path_tuple """
def path_for(objectid):
Expand Down Expand Up @@ -61,11 +59,8 @@ class ICatalog(Interface):
objectids = Attribute(
'a sequence of objectids that are cataloged in this catalog')

taggedValue('icon', 'icon-search')

class ISite(IPropertied):
""" Marker interface for something that is the root of a site """
taggedValue('icon', 'icon-home')

class ISearch(Interface):
""" Adapter for searching the catalog """
Expand Down Expand Up @@ -106,9 +101,6 @@ class IFolder(Interface):
name to either be Unicode or a byte string decodable using the
default system encoding or the UTF-8 encoding."""

taggedValue('icon', 'icon-folder-close')


order = Attribute("""Order of items within the folder
(Optional) If not set on the instance, objects are iterated in an
arbitrary order based on the underlying data store.""")
Expand Down Expand Up @@ -237,23 +229,18 @@ def remove(name, send_events=True):

class IUser(IPropertied):
""" Marker interface representing a user """
taggedValue('icon', 'icon-user')

class IGroup(IPropertied):
""" Marker interface representing a group """
taggedValue('icon', 'icon-th-list')

class IUsers(Interface):
""" Marker interface representing a collection of users """
taggedValue('icon', 'icon-list-alt')

class IGroups(Interface):
""" Marker interface representing a collection of groups """
taggedValue('icon', 'icon-list-alt')

class IPrincipals(Interface):
""" Marker interface representing a container of users and groups """
taggedValue('icon', 'icon-lock')

marker = object()

Expand Down
2 changes: 1 addition & 1 deletion substanced/objectmap/__init__.py
Expand Up @@ -83,7 +83,7 @@

_marker = object()

@content(IObjectMap)
@content(IObjectMap, icon='icon-asterisk')
class ObjectMap(Persistent):

_v_nextid = None
Expand Down

0 comments on commit 4db65c4

Please sign in to comment.