[Products.Silva][Sylvain Viollon] Refactor icon support to make ...
[email protected] Tue, 27 Aug 2013 18:23:33 +0200
| Newsgroups | gmane.comp.web.zope.silva.cvs |
|---|---|
| Message-ID | <[email protected]> |
author: Sylvain Viollon
date: Tue Aug 27 18:23:30 2013 +0200
revision: 11852:7c450b1e9276 in Products.Silva
branch: 2.4
details: https://hg.infrae.com/Products.Silva?cmd=changeset;node=7c450b1e9276
modified: Products/Silva/SimpleMembership.py Products/Silva/icon.py Products/Silva/tests/test_icon.py
added:
removed:
log: Refactor icon support to make them more customizable.
diffstat:
Products/Silva/SimpleMembership.py | 23 ++++
Products/Silva/icon.py | 199 +++++++++++++++++++++++-------------
Products/Silva/tests/test_icon.py | 134 +++++++++++-------------
3 files changed, 208 insertions(+), 148 deletions(-)
diffs (525 lines):
diff -r 30d0bbee6340 -r 7c450b1e9276 Products/Silva/SimpleMembership.py
--- a/Products/Silva/SimpleMembership.py Tue Aug 27 11:53:08 2013 +0200
+++ b/Products/Silva/SimpleMembership.py Tue Aug 27 18:23:30 2013 +0200
@@ -2,6 +2,9 @@
# Copyright (c) 2003-2013 Infrae. All rights reserved.
# See also LICENSE.txt
+import urllib
+import hashlib
+
from five import grok
# zope
@@ -13,6 +16,7 @@
# silva
from Products.Silva import SilvaPermissions
from Products.Silva import roleinfo
+from Products.Silva.icon import Icon
from Products.Silva.Membership import cloneMember, Member
from Products.Silva.Security import Security
from Products.Silva.helpers import add_and_edit
@@ -23,6 +27,25 @@
from silva.core import conf as silvaconf
from silva.translations import translate as _
+# This is not yet in use.
+GRAVATAR_URL = "https://secure.gravatar.com/avatar/"
+
+class GravatarIcon(Icon):
+
+ def __init__(self, icon):
+ self.icon = icon
+
+ def url(self, resolver, content):
+ if content is not None:
+ if interfaces.IAuthorization.providedBy(content):
+ content = content.source
+ email = content.avatar()
+ if email:
+ return (GRAVATAR_URL +
+ hashlib.md5(email.lower()).hexdigest() +
+ urllib.urlencode({'default': 'default', 'size': 16}))
+ return super(GravatarIcon, self).url(resolver, content)
+
class SimpleMember(Member, Security, ZMIObject):
"""Silva Simple Member"""
diff -r 30d0bbee6340 -r 7c450b1e9276 Products/Silva/icon.py
--- a/Products/Silva/icon.py Tue Aug 27 11:53:08 2013 +0200
+++ b/Products/Silva/icon.py Tue Aug 27 18:23:30 2013 +0200
@@ -2,6 +2,8 @@
# Copyright (c) 2003-2013 Infrae. All rights reserved.
# See also LICENSE.txt
+import types
+
from five import grok
from zope.cachedescriptors.property import Lazy
from zope.publisher.interfaces.browser import IBrowserRequest
@@ -11,88 +13,93 @@
from silva.core.views.interfaces import IVirtualSite
from silva.core.interfaces.adapters import IIconResolver
+_marker = object()
-class SilvaIcons(grok.DirectoryResource):
- # This export the globals directory using Zope 3 technology.
+class IconDirectory(grok.DirectoryResource):
+ # This export the globals directory containing all the default
+ # icon using Grok.
grok.path('icons')
grok.name('silva.icons')
+class Icon(object):
+ """Define a simple icon, its value, how to compute its URL and
+ render a tag.
+ """
+ grok.implements(interfaces.IIcon)
+ template = """<img height="16" width="16" src="{url}" alt="{alt}" />"""
-# GRAVATAR_URL = "https://secure.gravatar.com/avatar.php?"
-# GRAVATAR_TEMPLATE = """
-# <img src="%(image)s" alt="%(userid)s's avatar" title="%(userid)s's avatar"
-# style="height: %(size)spx; width: %(size)spx" />
-# """
+ def __init__(self, icon, template=None, url=None):
+ self.icon = icon
+ if url:
+ self.get_url = types.MethodType(url, self)
+ if template:
+ self.template = template
-# security.declareProtected(SilvaPermissions.AccessContentsInformation,
-# 'avatar_tag')
-# def avatar_tag(self, size=32):
-# """HTML <img /> tag for the avatar icon
-# """
-# #See http://en.gravatar.com/site/implement/python
-# email = self.avatar()
-# default = self.get_root_url() + "/globals/avatar.png"
+ def get_url(self, resolver, content):
+ return '/'.join((resolver.root_url, self.icon))
-# if email:
-# url = GRAVATAR_URL + urllib.urlencode(
-# {'gravatar_id':hashlib.md5(email.lower()).hexdigest(),
-# 'default':default, 'size':str(size)})
-# else:
-# url = default
-# info = {'userid': self.userid(),
-# 'size': size,
-# 'image': url}
-# return GRAVATAR_TEMPLATE % info
+ def __str__(self):
+ return str(self.icon)
+
+
+class IconSprite(object):
+ """Define an icon sprite that let you override the default icon
+ set registered in Silva. You have the possibility to nest then in
+ order to reuse customizations defined in an another sprites.
+ """
+
+ def __init__(self, sprite, template=None, url=None, parent=_marker):
+ """
+ ``sprite`` is a dictionnary containing the customized icon
+ sub-url.
+ ``template`` is a Python string used to create a tag if
+ required.
+ ``url`` is a method called if the URL of the icon is
+ required. ``parent`` is the srpite that is extended.
+ """
+ if parent is _marker:
+ parent = registry
+ self._icons = {}
+ self._parent = parent
+ for key, icon in sprite.iteritems():
+ if not isinstance(key, tuple):
+ key = ('meta_type', key)
+ if not interfaces.IIcon.providedBy(icon):
+ icon = Icon(icon, template, url)
+ self._icons[key] = icon
+
+ def get(self, identifier, default=_marker):
+ icon = self._icons.get(identifier)
+ if icon is None:
+ return self._parent.get(identifier, default)
+ return icon
class IconRegistry(object):
+ """The icon registry is the default icon sprite of Silva.
+ """
grok.implements(interfaces.IIconRegistry)
def __init__(self):
self._icons = {}
- def get_icon(self, content):
- if interfaces.IGhost.providedBy(content):
- identifier = ('ghost', 'link_ok')
- elif interfaces.IGhostFolder.providedBy(content):
- if content.get_link_status() is None:
- if interfaces.IPublication.providedBy(content.get_haunted()):
- kind = 'publication'
- else:
- kind = 'folder'
- else:
- kind = 'link_broken'
- identifier = ('ghostfolder', kind)
- elif interfaces.IFile.providedBy(content):
- identifier = ('mime_type', content.get_mime_type())
- elif interfaces.ISilvaObject.providedBy(content):
- identifier = ('meta_type', content.meta_type)
- else:
- if content is None:
- return '++static++/silva.icons/missing.png'
- if interfaces.IAuthorization.providedBy(content):
- content = content.source
- meta_type = getattr(content, 'meta_type', None)
- if meta_type is None:
- raise ValueError(u"No icon for unknown object %r" % content)
- identifier = ('meta_type', meta_type)
- return self.get_icon_by_identifier(identifier)
-
- def get_icon_by_identifier(self, identifier):
+ def get(self, identifier, default=_marker):
icon = self._icons.get(identifier, None)
if icon is None:
- raise ValueError(u"No icon for %r" % repr(identifier))
+ if default is _marker:
+ raise ValueError(u"No icon for %r" % repr(identifier))
+ return default
return icon
- def register(self, identifier, icon_name):
+ def register(self, identifier, icon):
"""Register an icon.
-
- NOTE: this will overwrite previous icon declarations
"""
assert isinstance(identifier, tuple) and len(identifier) == 2, \
'Invalid icon identifier'
- self._icons[identifier] = icon_name
+ if not interfaces.IIcon.providedBy(icon):
+ icon = Icon(icon)
+ self._icons[identifier] = icon
@apply
@@ -152,12 +159,13 @@
'++static++/silva.icons/%s' % icon_name)
misc_icons = [
+ ('default', None, 'generic.gif'),
('meta_type', None, 'missing.png'),
- ('ghostfolder', 'folder', 'ghost_folder.gif'),
- ('ghostfolder', 'publication', 'ghost_publication.gif'),
- ('ghostfolder', 'link_broken', 'ghost_broken.png'),
- ('ghost', 'link_ok', 'ghost.gif'),
- ('ghost', 'link_broken', 'ghost_broken.png'),
+ ('meta_type', 'Silva Ghost Folder', 'ghost_folder.gif'),
+ ('meta_type', 'Silva Ghost Publication', 'ghost_publication.gif'),
+ ('meta_type', 'Broken Silva Ghost Folder', 'ghost_broken.png'),
+ ('meta_type', 'Silva Ghost', 'ghost.gif'),
+ ('meta_type', 'Broken Silva Ghost', 'ghost_broken.png'),
]
for cls, kind, icon_name in misc_icons:
registry.register(
@@ -168,47 +176,88 @@
class IconResolver(grok.Adapter):
+ """Resolve and return an icon.
+ """
grok.context(IBrowserRequest)
grok.implements(IIconResolver)
- default = '++static++/silva.icons/generic.gif'
+ sprite = registry
def __init__(self, request):
self.request = request
@Lazy
- def _base_url(self):
+ def root_url(self):
site = IVirtualSite(self.request)
return site.get_root_url()
def get_tag(self, content=None, identifier=None):
if content is not None:
- url = self.get_content_url(content)
+ icon = self.get_content(content)
alt = getattr(content, 'meta_type', 'Missing')
else:
- url = self.get_identifier_url(identifier)
+ icon = self.get_identifier(identifier)
alt = identifier or 'Missing'
- return """<img height="16" width="16" src="%s" alt="%s" />""" % (
- url, alt)
+ if icon is not None:
+ return icon.template.format(
+ url=icon.get_url(self, content), alt=alt)
+ return u''
def get_identifier(self, identifier):
try:
- return registry.get_icon_by_identifier(('meta_type', identifier))
+ return self.sprite.get(('meta_type', identifier))
except ValueError:
- return self.default
+ return self.sprite.get(('default', None), default=None)
def get_content(self, content):
+ identifier = ('meta_type', None)
+ default = ('default', None)
try:
- return registry.get_icon(content)
+ if interfaces.IGhost.providedBy(content):
+ viewable = content.get_viewable()
+ if (viewable is not None and
+ viewable.get_link_status() is not None):
+ identifier = ('meta_type', 'Broken Silva Ghost')
+ else:
+ identifier = ('meta_type', 'Silva Ghost')
+ elif interfaces.IGhostFolder.providedBy(content):
+ if content.get_link_status() is None:
+ if interfaces.IPublication.providedBy(content.get_haunted()):
+ identifier = ('meta_type', 'Silva Ghost Publication')
+ else:
+ identifier = ('meta_type', 'Silva Ghost Folder')
+ else:
+ identifier = ('meta_type', 'Broken Silva Ghost Folder')
+ elif interfaces.IFile.providedBy(content):
+ identifier = ('mime_type', content.get_mime_type())
+ default = ('meta_type', 'Silva File')
+ elif interfaces.ISilvaObject.providedBy(content):
+ identifier = ('meta_type', content.meta_type)
+ elif content is None:
+ default = ('meta_type', None)
+ else:
+ if interfaces.IAuthorization.providedBy(content):
+ content = content.source
+ meta_type = getattr(content, 'meta_type', None)
+ if meta_type is None:
+ raise ValueError(u"No icon for unknown object %r" % content)
+ identifier = ('meta_type', meta_type)
+ return self.sprite.get(identifier)
except ValueError:
- return self.default
+ return self.sprite.get(default, default=None)
def get_content_url(self, content):
"""Return a content icon URL.
"""
- return "/".join((self._base_url, self.get_content(content),))
+ icon = self.get_content(content)
+ if icon is not None:
+ return icon.get_url(self, content)
+ return None
def get_identifier_url(self, identifier):
"""Return a URL out of a identifier.
"""
- return "/".join((self._base_url, self.get_identifier(identifier),))
+ icon = self.get_identifier(identifier)
+ if icon is not None:
+ return icon.get_url(self, None)
+ return None
diff -r 30d0bbee6340 -r 7c450b1e9276 Products/Silva/tests/test_icon.py
--- a/Products/Silva/tests/test_icon.py Tue Aug 27 11:53:08 2013 +0200
+++ b/Products/Silva/tests/test_icon.py Tue Aug 27 18:23:30 2013 +0200
@@ -5,13 +5,11 @@
# Python
import unittest
-# Silva
-from silva.core import interfaces
-from silva.core.interfaces.adapters import IIconResolver
+from silva.core.interfaces import IIconRegistry, IIconResolver, IIcon
from zope.component import queryAdapter
from zope.interface.verify import verifyObject
-from Products.Silva.icon import IconRegistry, registry
+from Products.Silva.icon import registry
from Products.Silva.testing import FunctionalLayer, TestRequest
@@ -27,35 +25,64 @@
self.root.pdf.set_content_type('application/pdf')
self.root.text.set_content_type('text/plain')
- def test_icon_url(self):
+ def test_icon_resolver(self):
"""Test adapter to retrieve icon urls'.
"""
resolver = queryAdapter(TestRequest(), IIconResolver)
self.assertTrue(verifyObject(IIconResolver, resolver))
+ icon = resolver.get_content(self.root)
+ self.assertTrue(verifyObject(IIcon, icon))
self.assertEqual(
- resolver.get_content(self.root),
+ str(icon),
'++resource++icon-Silva-Root.png')
+ icon = resolver.get_identifier('Silva Root')
+ self.assertTrue(verifyObject(IIcon, icon))
+ self.assertEqual(
+ str(icon),
+ '++resource++icon-Silva-Root.png')
+ icon = resolver.get_identifier(None) # Missing
+ self.assertTrue(verifyObject(IIcon, icon))
+ self.assertEqual(
+ str(icon),
+ '++static++/silva.icons/missing.png')
+ icon = resolver.get_identifier('Best content in the world') # Generic
+ self.assertTrue(verifyObject(IIcon, icon))
+ self.assertEqual(
+ str(icon),
+ '++static++/silva.icons/generic.gif')
+
self.assertEqual(
resolver.get_content_url(self.root),
'http://localhost/root/++resource++icon-Silva-Root.png')
self.assertEqual(
- resolver.get_identifier('Silva Root'),
- '++resource++icon-Silva-Root.png')
+ resolver.get_content_url(None),
+ 'http://localhost/root/++static++/silva.icons/missing.png')
self.assertEqual(
resolver.get_identifier_url('Silva Root'),
'http://localhost/root/++resource++icon-Silva-Root.png')
+ self.assertEqual(
+ resolver.get_identifier_url(None),
+ 'http://localhost/root/++static++/silva.icons/missing.png')
+ self.assertEqual(
+ resolver.get_identifier_url('best content in the world'),
+ 'http://localhost/root/++static++/silva.icons/generic.gif')
+ icon = resolver.get_content(self.root.pdf)
+ self.assertTrue(verifyObject(IIcon, icon))
self.assertEqual(
- resolver.get_content(self.root.pdf),
+ str(icon),
'++static++/silva.icons/file_pdf.png')
+ icon = resolver.get_content(self.root.text)
+ self.assertTrue(verifyObject(IIcon, icon))
+ self.assertEqual(
+ str(icon),
+ '++static++/silva.icons/file_txt.png')
+
self.assertEqual(
resolver.get_content_url(self.root.pdf),
'http://localhost/root/++static++/silva.icons/file_pdf.png')
self.assertEqual(
- resolver.get_content(self.root.text),
- '++static++/silva.icons/file_txt.png')
- self.assertEqual(
resolver.get_content_url(self.root.text),
'http://localhost/root/++static++/silva.icons/file_txt.png')
@@ -85,79 +112,40 @@
def test_default_icons(self):
"""Test default registered icons.
"""
- # Silva content types
+ self.assertTrue(verifyObject(IIconRegistry, registry))
+
+ icon = registry.get(('meta_type', 'Silva Link'))
+ self.assertTrue(verifyObject(IIcon, icon))
self.assertEqual(
- registry.get_icon(self.root),
- '++resource++icon-Silva-Root.png')
- self.assertEqual(
- registry.get_icon_by_identifier(
- ('meta_type', 'Silva Link')),
+ str(icon),
'++resource++icon-Silva-Link.png' )
- # Simple member icons
- member = self.root.service_members.get_member('author')
+ icon = registry.get(('meta_type', 'Silva Simple Member'))
+ self.assertTrue(verifyObject(IIcon, icon))
self.assertEqual(
- registry.get_icon(member),
+ str(icon),
'++resource++icon-Silva-Simple-Member.png')
- # File icons
+ icon = registry.get(('mime_type', 'application/pdf'))
+ self.assertTrue(verifyObject(IIcon, icon))
self.assertEqual(
- registry.get_icon(self.root.pdf),
+ str(icon),
'++static++/silva.icons/file_pdf.png')
+
+ icon = registry.get(('mime_type', 'text/plain'))
+ self.assertTrue(verifyObject(IIcon, icon))
self.assertEqual(
- registry.get_icon(self.root.text),
+ str(icon),
'++static++/silva.icons/file_txt.png')
- def test_member_gravatar(self):
- """Test that if you ask the icon for a member you can a
- gravatar icon.
- """
- # XXX This will will be implemented in Silva 3.1
- # self.assertXMLEqual(
- # user.avatar_tag(),
- # '<img src="' + self.root.get_root_url() + '/globals/avatar.png" alt="manager\'s avatar" title="manager\'s avatar" style="height: 32px; width: 32px" />')
- # user.set_email('[email protected]')
- # self.assertEqual('[email protected]', user.avatar())
- # self.assertXMLEqual(
- # user.avatar_tag(),
- # '<img src="https://secure.gravatar.com/avatar.php?default=' + urllib.quote(self.root.get_root_url(),'') + '%2Fglobals%2Favatar.png&size=32&gravatar_id=b58996c504c5638798eb6b511e6f49af" alt="manager\'s avatar" title="manager\'s avatar" style="height: 32px; width: 32px" />')
+ with self.assertRaises(ValueError):
+ registry.get(('meta_type', 'Best content in the world'))
- def test_registry(self):
- """Test registry
- """
- self.assertEqual(
- self.root.pdf.get_mime_type(), 'application/pdf')
- self.assertEqual(
- self.root.text.get_mime_type(), 'text/plain')
-
- registry = IconRegistry()
- self.assertTrue(verifyObject(interfaces.IIconRegistry, registry))
-
- registry.register(
- ('meta_type', 'Silva Root'), 'root.png')
- registry.register(
- ('mime_type', 'text/plain'), 'file_text.png')
- registry.register(
- ('mime_type', 'application/octet-stream'), 'file.png')
- registry.register(
- ('mime_type', 'application/pdf'), 'file_pdf.png')
-
- self.assertEqual(
- registry.get_icon_by_identifier(
- ('meta_type', 'Silva Root')),
- 'root.png')
- self.assertEqual(
- registry.get_icon_by_identifier(
- ('mime_type', 'application/octet-stream')),
- 'file.png')
- with self.assertRaises(ValueError):
- registry.get_icon_by_identifier(('meta_type', 'Foo Bar'),)
-
- self.assertEqual(registry.get_icon(self.root), 'root.png')
- self.assertEqual(registry.get_icon(self.root.pdf), 'file_pdf.png')
- self.assertEqual(registry.get_icon(self.root.text), 'file_text.png')
- with self.assertRaises(ValueError):
- registry.get_icon(TestRequest())
+ marker = object()
+ icon = registry.get(
+ ('meta_type', 'Best content in the world'),
+ default=marker)
+ self.assertIs(icon, marker)
def test_suite():